Phase1 与 BuildPipeline 各自把 PYI_BUF_SIZE 声明为模块级全局,模块级全局走裸名符号(类 C extern 全局语义),两模块各发一份强定义 @PYI_BUF_SIZE,llvm-link 报 symbol multiply defined 并回退到单独编译。改为编译期宏 define PYI_BUF_SIZE = 262144(与同文件 define SRC_BUF_SIZE 同口径,不产生符号),同步更新引用该符号的非 CDefine 示例注释。project.vpj 移除 -Wl,--allow-multiple-definition 兜底。验证:跨模块内联优化完成,最终链接无该 flag 成功(161.70s);TransPyV 自举产物顺序回归 77/77 全通过。
2708 lines
123 KiB
Plaintext
2708 lines
123 KiB
Plaintext
import lib.core.Gen.GenApi as GenApi
|
||
import t, c
|
||
from stdint import *
|
||
import stdio
|
||
import string
|
||
import stdlib
|
||
import memhub
|
||
import viperlib
|
||
import vthreading
|
||
import w32.fileio as fileio
|
||
import w32.win32file
|
||
import w32.win32base
|
||
import w32.win32process as win32process
|
||
import w32.win32sync as win32sync
|
||
import subprocess
|
||
import viperlib
|
||
import ast
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.Translator as HandlesTranslator
|
||
import lib.core.Gen.DebugInfoGen as DebugInfoGen
|
||
import lib.Stub.StubMerger as StubMerger
|
||
import lib.Stub.StubGen.Converter as StubConverter
|
||
import lib.Projectrans.Config as Config
|
||
import lib.core.Gen.Generator as GenGenerator
|
||
import lib.core.Gen.Expr.ExprGenCls as ExprGenCls
|
||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||
|
||
# ============================================================
|
||
# BuildPipeline - 编译管线
|
||
#
|
||
# 负责将 LLVM IR 编译为可执行文件:
|
||
# 1. 写 .ll 文件到 temp 目录
|
||
# 2. 调用 llc 编译 .ll → .obj
|
||
# 3. 调用 clang++ 链接 .obj → .exe
|
||
# ============================================================
|
||
|
||
# 全局 mbuddy 指针
|
||
_mbuddy: memhub.MemBuddy*
|
||
|
||
# ASan 模式(--asan):对 .ll 先 opt -passes=asan 插桩;ASan 运行库由
|
||
# project.vpj 的 linker.static_libs / dynamic_libs 显式声明(TPV 不注入)
|
||
_asan_mode: bool = 0
|
||
|
||
# Traceback 模式(--traceback):强制编入 includes/traceback.vp 并注入启动期
|
||
# Install() 构造器;llc 保留帧指针(-frame-pointer=all);链接 dbghelp;
|
||
# 链接成功后导出 <exe>.stackmap 地址→源行旁文件。
|
||
_traceback_mode: bool = 0
|
||
|
||
# --phase2command xxx.bat:不执行 llc/链接命令,只把 Phase B/B+/C 的
|
||
# llc(含 ASan opt)与 clang 链接命令写入指定 .bat 路径(由用户手动执行)。
|
||
# None = 正常执行(默认)。
|
||
_phase2_cmd_path: str = None
|
||
# bat 文件是否已写入 header(@echo off),首次写入时置 1
|
||
_phase2_bat_init: int = 0
|
||
|
||
# MLIR 降级步骤(单端固定,无后端开关):mlirlite 发射的是 MLIR 文本,
|
||
# 而 opt/llc 只接受 LLVM IR,故在两者之前插入:
|
||
# mlir-opt <in> -o <in>.mlir.opt (语法/语义校验)
|
||
# mlir-translate --mlir-to-llvmir <in>.mlir.opt -o <in>.ll.local (降级回 LLVM IR)
|
||
# 降级产物 <...>.ll.local 与输入 .ll 同目录,不占用 <sha1>.ll 缓存键。
|
||
MLIR_OPT_CMD: str = "mlir-opt"
|
||
MLIR_TRANSLATE_CMD: str = "mlir-translate"
|
||
|
||
|
||
def _ir_sibling_path(ir_path: str, suffix: str) -> str:
|
||
"""由 <...>.ll 派生同目录邻居路径:去掉末尾 ".ll" 后拼 suffix(如 ".mlir.opt")。
|
||
|
||
从 _mbuddy 分配(与 build_sliced_path 同池,随 pool reset 回收,无需手动 free)。
|
||
"""
|
||
if ir_path is None or suffix is None: return None
|
||
n: sizet = string.strlen(ir_path)
|
||
if n < 3: return None
|
||
suf_len: sizet = string.strlen(suffix)
|
||
buf: str = _mbuddy.alloc(n + suf_len + 1)
|
||
if buf is None: return None
|
||
string.strncpy(buf, ir_path, n - 3)
|
||
buf[n - 3] = '\0'
|
||
string.strcat(buf, suffix)
|
||
return buf
|
||
|
||
# 源代码缓冲区大小(1MB)
|
||
define SRC_BUF_SIZE = 1048576 # 2^20 = 1MB
|
||
|
||
# .vpi 存根缓冲区大小(256KB,与 Phase1 一致)
|
||
# 编译期常量用 define:模块级同名全局(Phase1 已有 PYI_BUF_SIZE)会各发一份
|
||
# 强定义符号,llvm-link 报 symbol multiply defined。
|
||
define PYI_BUF_SIZE = 262144
|
||
|
||
# ============================================================
|
||
# _llc_frame_ptr_flag - llc extra flag for --traceback
|
||
#
|
||
# Returns " -frame-pointer=all" (leading space so it concatenates into the llc
|
||
# flag sequence) while --traceback is on; "" otherwise, keeping the llc command
|
||
# byte-identical to the pre-switch behaviour.
|
||
# ============================================================
|
||
def _llc_frame_ptr_flag() -> str:
|
||
if _traceback_mode != 0: return " -frame-pointer=all"
|
||
return ""
|
||
|
||
|
||
# ============================================================
|
||
# _append_traceback_dbghelp - append -ldbghelp for --traceback
|
||
#
|
||
# traceback.vp's main walk path uses StackWalk64 / SymInitialize /
|
||
# SymFunctionTableAccess64 / SymGetModuleBase64 (all in dbghelp); without this
|
||
# flag the link fails with undefined symbols. Libraries must follow the objects,
|
||
# so this is appended to linker_flags (not prepended).
|
||
# ============================================================
|
||
def _append_traceback_dbghelp(linker_flags: str) -> str:
|
||
if _traceback_mode == 0 or linker_flags is None: return linker_flags
|
||
need: sizet = string.strlen(linker_flags) + 16
|
||
buf: str = _mbuddy.alloc(need)
|
||
if buf is None:
|
||
VLogger.error("--traceback: -ldbghelp 链接参数缓冲分配失败,终止编译", "TRACEBACK")
|
||
return linker_flags
|
||
viperlib.snprintf(buf, need, "%s -ldbghelp", linker_flags)
|
||
return buf
|
||
|
||
|
||
# ============================================================
|
||
# --traceback 调用点旁文件(<sha1>.vpcall)
|
||
#
|
||
# 编译器(mlirlite 调试调用点记录 + DebugInfoGen 转达)在翻译每个模块时累积
|
||
# 「F/C」记录文本;Phase1/Phase2 在模块翻译完成、dump 之后调用 WriteCallSidecar
|
||
# 写盘并登记路径。WriteStackMap 链接后按登记表读取,结合 llvm-objdump 归一地址。
|
||
# 仅 --traceback 时产生;关闭时不写任何旁文件、不登记。
|
||
# ============================================================
|
||
define TB_VPCALL_MAX = 8192
|
||
_tb_vpcall_buf: bytes = None # TB_VPCALL_MAX * 8:每槽一个 char*(stdlib 分配,进程内一次性)
|
||
_tb_vpcall_n: int = 0
|
||
|
||
|
||
def _tb_register_vpcall(path: str) -> int:
|
||
"""把已写出的 .vpcall 路径登记进全局表(供 WriteStackMap 稍后读取)。"""
|
||
global _tb_vpcall_buf, _tb_vpcall_n
|
||
if path is None: return 0
|
||
if _tb_vpcall_buf is None:
|
||
_tb_vpcall_buf = stdlib.malloc(sizet(TB_VPCALL_MAX) * 8)
|
||
if _tb_vpcall_buf is None:
|
||
VLogger.error("--traceback: .vpcall 路径表分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
_tb_vpcall_n = 0
|
||
if _tb_vpcall_n >= TB_VPCALL_MAX:
|
||
VLogger.error("--traceback: .vpcall 路径表溢出(模块数超上限),终止编译", "TRACEBACK")
|
||
return 1
|
||
slot: str* = (str*)(uint64(_tb_vpcall_buf) + sizet(_tb_vpcall_n) * 8)
|
||
slot[0] = path
|
||
_tb_vpcall_n += 1
|
||
return 0
|
||
|
||
|
||
def WriteCallSidecar(temp_dir: str, sha1: str, text: str) -> int:
|
||
"""--traceback: 把本模块的调试调用点文本写入 <temp_dir>/<slice>/<sha1>.vpcall。
|
||
|
||
text 为 None 时跳过(该模块无 define/无调用)。路径随后登记到全局表,交由
|
||
WriteStackMap 读取;本函数不释放路径(进程生命周期内一直有效)。
|
||
"""
|
||
if temp_dir is None or sha1 is None or text is None: return 0
|
||
vpath: str = StubMergerCollect._sliced_path(temp_dir, string.strlen(temp_dir), sha1, "vpcall")
|
||
if vpath is None:
|
||
VLogger.error(f"--traceback: .vpcall 路径构造失败(sha1={sha1}),终止编译", "TRACEBACK")
|
||
return 1
|
||
vf: rawptr[fileio.File] = fileio.File(vpath, fileio.MODE.W)
|
||
if vf.closed:
|
||
VLogger.error(f"--traceback: 写出 .vpcall 失败(无法打开 {vpath}),终止编译", "TRACEBACK")
|
||
stdlib.free(vpath)
|
||
return 1
|
||
vf.write(text, string.strlen(text))
|
||
vf.close()
|
||
return _tb_register_vpcall(vpath)
|
||
|
||
|
||
# ============================================================
|
||
# _tb_basename_is_traceback - is this source file includes/traceback.vp
|
||
# ============================================================
|
||
def _tb_basename_is_traceback(file_path: str) -> int:
|
||
if file_path is None: return 0
|
||
flen: sizet = string.strlen(file_path)
|
||
if flen < 12: return 0
|
||
if flen > 12:
|
||
prev: char = file_path[flen - 13]
|
||
if prev != '/' and prev != '\\': return 0
|
||
return 1 if string.strcmp(file_path + (flen - 12), "traceback.vp") == 0 else 0
|
||
|
||
# ============================================================
|
||
# TranslateFileGetTrans - 翻译单个文件,返回 Translator 对象(供调用者 dump_ir)
|
||
#
|
||
# 读取文件 → AST 解析 → .vpi 生成 → LLVM IR 翻译。命名空间隔离:includes 文件宽松模式(全部可见),用户文件严格模式(仅本地+import)。
|
||
#
|
||
# Args: mb 内存池;file_path 源文件路径;sha1_val 文件内容的 SHA1 前16字符(用于函数名混淆);current_package 当前文件所属包名(用于解析相对导入),None 表示顶级模块;declare_only 0=全量翻译(默认),1=仅注册 struct/enum/union(不翻译方法体);temp_dir temp 目录路径(非 None 时在翻译前生成 .vpi 存根文件)
|
||
# Returns: Translator 对象(None 失败)
|
||
# ============================================================
|
||
def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, current_package: str = None, declare_only: int = 0, temp_dir: str = None, pre_allow_dep_inject: int = 0) -> rawptr[HandlesTranslator.Translator]:
|
||
"""翻译文件,返回 Translator 对象(调用者负责 dump_ir),None 失败
|
||
|
||
current_package: 当前文件所属包名(用于解析相对导入),None 表示顶级模块
|
||
declare_only: 0=全量翻译(默认),1=仅注册 struct/enum/union(不翻译方法体)
|
||
temp_dir: temp 目录路径(非 None 时在翻译前生成 .vpi 存根文件)
|
||
pre_allow_dep_inject: 仅 **App 预注册**(Phase A-pre)传 1:声明期也处理导入语句并
|
||
注入依赖 .vpi,使类字段注解与阶段 A 同上下文(详见 Translator._pre_allow_dep_inject)。
|
||
includes 预注册(Phase A-pre-inc)必须保持 0。
|
||
"""
|
||
if file_path is None: return None
|
||
f: rawptr[fileio.File] = fileio.File(file_path, fileio.MODE.R)
|
||
if f.closed: return None
|
||
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**;
|
||
# 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误
|
||
if f.size() >= SRC_BUF_SIZE:
|
||
VLogger.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {file_path}", "fileio")
|
||
f.close()
|
||
return None
|
||
src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE)
|
||
if src_buf is None:
|
||
f.close()
|
||
return None
|
||
bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE)
|
||
f.close()
|
||
if bytes_read <= 0:
|
||
stdlib.free(src_buf)
|
||
return None
|
||
if bytes_read < SRC_BUF_SIZE: src_buf[bytes_read] = 0
|
||
lx: ast.Lexer* = ast.new_lexer(mb)
|
||
ast._lexer_init(lx, src_buf, mb)
|
||
tokens: ast.Token* = ast.tokenize(lx)
|
||
tree: ast.AST* = ast.parse_tokens(mb, tokens)
|
||
# parse 完成后 AST 树已独立,释放源代码缓冲区(避免 1MB/文件泄漏)
|
||
stdlib.free(src_buf)
|
||
if tree is None:
|
||
# fail-fast 定位:以 Python 风格输出解析错误 traceback(仿 fatal_error)
|
||
# Traceback (most recent call last):
|
||
# File "<path>", line N
|
||
# <source line>
|
||
# SyntaxError: <message>
|
||
GenApi.dump_parse_traceback(file_path)
|
||
err_line: int = ast.get_parse_err_line()
|
||
err_col: int = ast.get_parse_err_col()
|
||
VLogger.error(f"解析失败: {file_path} (line {err_line} col {err_col})", "PARSE")
|
||
return None
|
||
# 生成 .vpi 存根文件(从 AST 直接生成,在 tr.translate 之前,与 Phase1 一致)
|
||
# .vpi 供其他模块跨模块查询函数签名和 CTypedef 定义
|
||
if temp_dir is not None:
|
||
pyi_buf: bytes = stdlib.malloc(PYI_BUF_SIZE)
|
||
if pyi_buf is not None:
|
||
pyi_buf[0] = 0
|
||
# rel_path 传 file_path(本文件绝对路径):vpi 生成期间 fatal_error
|
||
# 的报错归因需指向本源文件(Phase A 预注册/翻译时 file_path 已可用),
|
||
# 且绝对路径可被 fatal_error 的 _read_source_line 直接打开打印源码行。
|
||
pyi_pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, file_path, pyi_buf, PYI_BUF_SIZE)
|
||
if pyi_pos > 0:
|
||
# 写入 {sha1}.vpi 到 temp_dir(切片路径)
|
||
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: rawptr[fileio.File] = fileio.File(pyi_path, fileio.MODE.W)
|
||
if not pf.closed:
|
||
pf.write(pyi_buf, pyi_pos)
|
||
pf.close()
|
||
stdlib.free(pyi_path)
|
||
stdlib.free(pyi_buf)
|
||
tr: rawptr[HandlesTranslator.Translator] = mb.alloc(HandlesTranslator.Translator.__sizeof__())
|
||
tr.__before_init__()
|
||
tr.__init__()
|
||
tr.ModuleSha1 = sha1_val
|
||
tr.CurrentPackage = current_package
|
||
tr._declare_only = declare_only
|
||
# App 预注册(Phase A-pre)专用开关:置 1 时声明期也做导入处理 + 依赖 vpi 注入
|
||
# (详见 Translator._pre_allow_dep_inject)。includes 预注册(Phase A-pre-inc)
|
||
# 必须传 0,否则依赖环自锁(见 Translator 内 _declare_only==0 门处注释)。
|
||
tr._pre_allow_dep_inject = pre_allow_dep_inject
|
||
# P2: 复用全局 gen 单例(App/main.vp 创建;HandlesType 等旧模块函数经
|
||
# 延迟 import 委托到 gen 方法)。单例未注册时兜底创建(防御直接调用路径)。
|
||
gen: rawptr[GenGenerator.Generator] = GenGenerator._g_gen
|
||
if gen is None:
|
||
gen = mb.alloc(GenGenerator.Generator.__sizeof__())
|
||
if gen is None:
|
||
VLogger.error("Generator alloc failed", "GEN")
|
||
return None
|
||
gen.__before_init__()
|
||
gen.__init__(mb)
|
||
GenGenerator._g_gen = gen
|
||
# P2-P5: 注入各 Handles 模块级单例(委托函数经本地 _g_gen 访问,
|
||
# 避免函数体 import 包模块与类/模块名歧义)
|
||
GenApi._g_gen = gen
|
||
# P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例
|
||
# 设置当前文件名(供报错使用)
|
||
GenApi.set_current_file_type(file_path)
|
||
# --traceback: mirror the switch (BuildPipeline._traceback_mode) into the
|
||
# Gen-layer DWARF emitter. 0 => every DI entry point is a no-op and the IR
|
||
# stays byte-identical to the pre-DWARF output.
|
||
DebugInfoGen.SetEnabled(_traceback_mode)
|
||
# 同步到 HandlesImports(import 时 SHA1 检查报错需要当前文件名,循环依赖无法直接读 HandlesType)
|
||
GenApi.set_current_file_imports(file_path)
|
||
# 清空调用栈(每个文件翻译开始前重置)
|
||
GenApi.clear_trace()
|
||
# 模块切换:清空 CDefine 常量表,确保每个模块的 CDefine 常量正确隔离
|
||
GenApi.clear_cdefine_constants()
|
||
# 模块切换:清空 CTypedef 表,确保每个模块的 CTypedef 正确隔离
|
||
GenApi.clear_ctypedefs()
|
||
# 命名空间隔离:includes 文件宽松模式(全部可见),用户文件严格模式(仅本地+import)
|
||
strict_mode: int = 1
|
||
if string.strstr(file_path, "includes") is not None: strict_mode = 0
|
||
GenApi.reset_visible_structs(mb, strict_mode)
|
||
# 提取 docstring(预注册阶段所有 App/includes 文件都被翻译,保证跨模块 __doc__ 在正式翻译前已登记;查重幂等)
|
||
# 必须在 tr.translate 之前扫描:translate 期间 mb 池大量分配 IR 节点,AST 节点所在池块可能被复用覆盖(ASan 下遍历 tree 会崩溃)。
|
||
if gen is not None: gen.ExprGenCls.extract_module_docstrings(tree, sha1_val)
|
||
ret: int = tr.translate(tree)
|
||
if ret != 0:
|
||
VLogger.error(f"文件翻译失败: {file_path}", "Translate")
|
||
return None
|
||
# --traceback: when this file is includes/traceback.vp, inject the startup
|
||
# Install() ctor into its IR (before the caller dumps stub/text.ll). The
|
||
# injector lives in the Gen layer, which already depends on mlirlite, so
|
||
# BuildPipeline gains no cross-module mlirlite symbol dependency.
|
||
if _traceback_mode != 0 and _tb_basename_is_traceback(file_path) != 0: HandlesTranslator.InjectTracebackInstallCtor(tr)
|
||
return tr
|
||
|
||
|
||
class BuildResult:
|
||
Success: int
|
||
OutputPath: str
|
||
ErrorMsg: str
|
||
|
||
def __new__(self) -> t.CPtr:
|
||
return t.CPtr(_mbuddy.alloc(BuildResult.__sizeof__()))
|
||
|
||
def __init__(self):
|
||
self.Success = 0
|
||
self.OutputPath = None
|
||
self.ErrorMsg = None
|
||
|
||
|
||
def ensure_dir(path: str) -> int:
|
||
"""递归创建目录(类似 mkdir -p),目录已存在视为成功
|
||
|
||
Args:
|
||
path: 目录路径(支持 / 或 \\ 分隔符)
|
||
|
||
Returns:
|
||
0 成功(包括目录已存在),非 0 失败
|
||
"""
|
||
if path is None: return 1
|
||
|
||
path_len: sizet = len(path)
|
||
if path_len == 0: return 0
|
||
|
||
# 热点快速路径(PROF:ensure_dir 占 4.5%,22256 次调用每次逐级 CreateDirectoryA):
|
||
# 末级目录已存在则直接成功返回,省去 1 次分配 + N+1 次 CreateDirectoryA 系统调用。
|
||
# 语义不变:原实现把"目录已存在"(CreateDirectoryA 失败)也视为成功。
|
||
attr: ULONG = w32.win32file.GetFileAttributesA(path)
|
||
if attr != 0xFFFFFFFF and (attr & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: return 0
|
||
|
||
# 复制路径到可写缓冲区(逐级截断用)
|
||
buf: bytes = _mbuddy.alloc(path_len + 1)
|
||
if buf is None: return 1
|
||
string.strcpy(buf, path)
|
||
|
||
# 遇到分隔符时临时截断,创建每一层目录
|
||
# CreateDirectoryA 在目录已存在时返回 0(失败),忽略即可
|
||
plen: int = path_len
|
||
for i in range(plen):
|
||
ch: int = buf[i]
|
||
if ch == 47 or ch == 92: # '/' = 47, '\\' = 92
|
||
saved: int = ch
|
||
buf[i] = '\0'
|
||
w32.win32file.CreateDirectoryA(buf, None)
|
||
buf[i] = saved
|
||
|
||
# 创建最终目录
|
||
w32.win32file.CreateDirectoryA(buf, None)
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# build_sliced_path - 构建切片路径并确保目录存在(自动调用 ensure_dir)
|
||
#
|
||
# 按 Config.Sha1SliceLevel 分散到 SHA1 前缀子目录:level=0 → {base_dir}/{sha1}.{ext};level=1 → {base_dir}/b7/{sha1}.{ext};level=2 → {base_dir}/b7/90/{sha1}.{ext}
|
||
#
|
||
# Args: base_dir 基础目录(如 temp_dir);sha1 SHA1 字符串;ext 文件扩展名(如 "stub.ll")
|
||
# Returns: 完整文件路径(mbuddy 分配),None 失败
|
||
# ============================================================
|
||
def build_sliced_path(base_dir: str, sha1: str, ext: str) -> str:
|
||
"""构建切片路径并确保目录存在"""
|
||
if base_dir is None or sha1 is None or ext is None: return None
|
||
|
||
subdir: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
|
||
base_len: sizet = len(base_dir)
|
||
sha1_len: sizet = len(sha1)
|
||
ext_len: sizet = len(ext)
|
||
|
||
if subdir is not None:
|
||
sub_len: sizet = len(subdir)
|
||
# ensure_dir({base_dir}/{subdir})
|
||
dir_path: str = _mbuddy.alloc(base_len + sub_len + 2)
|
||
if dir_path is not None:
|
||
viperlib.snprintf(dir_path, base_len + sub_len + 2, "%s/%s", base_dir, subdir)
|
||
ensure_dir(dir_path)
|
||
# 文件路径: {base_dir}/{subdir}/{sha1}.{ext}
|
||
path_len: sizet = base_len + sub_len + sha1_len + ext_len + 4
|
||
path: str = _mbuddy.alloc(path_len)
|
||
if path is None: return None
|
||
viperlib.snprintf(path, path_len, "%s/%s/%s.%s", base_dir, subdir, sha1, ext)
|
||
return path
|
||
else:
|
||
# 无切片: {base_dir}/{sha1}.{ext}
|
||
path_len = base_len + sha1_len + ext_len + 3
|
||
path = _mbuddy.alloc(path_len)
|
||
if path is None: return None
|
||
viperlib.snprintf(path, path_len, "%s/%s.%s", base_dir, sha1, ext)
|
||
return path
|
||
|
||
|
||
def write_ir_to_file(ir_buf: bytes, ir_len: sizet, output_dir: str, module_name: str) -> int:
|
||
"""将 IR 缓冲区写入 .ll 文件
|
||
|
||
Args:
|
||
ir_buf: IR 文本缓冲区
|
||
ir_len: IR 文本长度
|
||
output_dir: 输出目录(temp 或 output)
|
||
module_name: 模块名(SHA1)
|
||
|
||
Returns:
|
||
0 成功,非 0 失败
|
||
"""
|
||
if ir_buf is None or output_dir is None or module_name is None: return 1
|
||
|
||
# 构造切片路径: output_dir/{sha1前缀}/{module_name}.ll
|
||
path: str = build_sliced_path(output_dir, module_name, "ll")
|
||
if path is None: return 1
|
||
|
||
# 打开文件写入
|
||
f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.W)
|
||
if f.closed: return 1
|
||
|
||
written: int64 = f.write(ir_buf, ir_len)
|
||
f.close()
|
||
if written < 0: return 1
|
||
return 0
|
||
|
||
|
||
def compile_ll_to_obj(ir_path: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int:
|
||
"""调用 llc 将 .ll 编译为 .obj
|
||
|
||
Args:
|
||
ir_path: .ll 文件路径
|
||
output_dir: 输出目录
|
||
module_name: 模块名(SHA1,用于生成 .obj 文件名)
|
||
cc_cmd: 编译器命令(如 "llc")
|
||
cc_flags: 编译器参数(如 "-filetype=obj -relocation-model=pic")
|
||
|
||
Returns:
|
||
0 成功,非 0 失败
|
||
"""
|
||
if ir_path is None or cc_cmd is None or output_dir is None or module_name is None: return 1
|
||
|
||
# 构造切片 .obj 路径: output_dir/{sha1前缀}/{module_name}.obj
|
||
obj_path: str = build_sliced_path(output_dir, module_name, "obj")
|
||
if obj_path is None: return 1
|
||
|
||
# ASan 模式(_asan_mode=1):先 opt -passes=asan 插桩再 llc。
|
||
# -asan-globals=false 关闭全局插桩:TPV 对全局变量(如各模块 _g_gen)
|
||
# 用直接地址寻址,ASan 全局 redzone 重定位后引用错位会读到垃圾指针。
|
||
compile_input: str = ir_path
|
||
|
||
# ---- MLIR 降级(单端固定,无开关):mlir-opt(校验)→ mlir-translate ----
|
||
# 必须在 ASan opt 与 llc 之前,并把 compile_input 替换为 <...>.ll.local。
|
||
ll_local_path: str = _ir_sibling_path(ir_path, ".ll.local")
|
||
mlir_opt_out: str = _ir_sibling_path(ir_path, ".mlir.opt")
|
||
if ll_local_path is None or mlir_opt_out is None: return 1
|
||
mo_len: sizet = len(MLIR_OPT_CMD) + len(ir_path) + len(mlir_opt_out) + 16
|
||
cmd_mo: str = _mbuddy.alloc(mo_len)
|
||
if cmd_mo is None: return 1
|
||
viperlib.snprintf(cmd_mo, mo_len, "%s %s -o %s", MLIR_OPT_CMD, ir_path, mlir_opt_out)
|
||
mt_len: sizet = len(MLIR_TRANSLATE_CMD) + len(mlir_opt_out) + len(ll_local_path) + 40
|
||
cmd_mt: str = _mbuddy.alloc(mt_len)
|
||
if cmd_mt is None: return 1
|
||
viperlib.snprintf(cmd_mt, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, ll_local_path)
|
||
if _phase2_cmd_path is not None:
|
||
# 只写命令(须排在下面的 ASan/llc 命令之前)
|
||
_phase2_write_cmd(cmd_mo)
|
||
_phase2_write_cmd(cmd_mt)
|
||
else:
|
||
mo_rc: int = _run_cmd_wait(cmd_mo)
|
||
if mo_rc != 0:
|
||
VLogger.error(f"mlir-opt 失败 (module={module_name}, exit={mo_rc}, cmd={cmd_mo})", "MLIR")
|
||
return 1
|
||
mt_rc: int = _run_cmd_wait(cmd_mt)
|
||
if mt_rc != 0:
|
||
VLogger.error(f"mlir-translate 失败 (module={module_name}, exit={mt_rc}, cmd={cmd_mt})", "MLIR")
|
||
return 1
|
||
compile_input = ll_local_path
|
||
|
||
# --phase2command 模式:不执行 opt/llc,把 opt(ASan)+ llc 命令写入 bat
|
||
if _phase2_cmd_path is not None:
|
||
p2_opt_cmd: str = None
|
||
if _asan_mode != 0:
|
||
p2_asan: str = build_sliced_path(output_dir, module_name, "asan.ll")
|
||
if p2_asan is not None:
|
||
p2_olen: sizet = len(p2_asan) + len(compile_input) + 192
|
||
p2_opt_cmd = _mbuddy.alloc(p2_olen)
|
||
if p2_opt_cmd is not None:
|
||
viperlib.snprintf(p2_opt_cmd, p2_olen, "opt -passes=forceattrs,asan -force-attribute=sanitize_address -asan-globals=false -o %s %s", p2_asan, compile_input)
|
||
compile_input = p2_asan
|
||
llc_fp: str = _llc_frame_ptr_flag()
|
||
p2_clen: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp) + len(obj_path) + len(compile_input) + 64
|
||
p2_cmd: bytes = _mbuddy.alloc(p2_clen)
|
||
if p2_cmd is not None:
|
||
viperlib.snprintf(p2_cmd, p2_clen, "%s %s%s -o %s %s", cc_cmd, cc_flags, llc_fp, obj_path, compile_input)
|
||
if p2_opt_cmd is not None: _phase2_write_cmd(p2_opt_cmd)
|
||
if p2_cmd is not None: _phase2_write_cmd(p2_cmd)
|
||
return 0
|
||
|
||
if _asan_mode != 0:
|
||
asan_path: str = build_sliced_path(output_dir, module_name, "asan.ll")
|
||
if asan_path is None: return 1
|
||
opt_cmd_len: sizet = len(asan_path) + len(compile_input) + 192
|
||
opt_cmd: bytes = _mbuddy.alloc(opt_cmd_len)
|
||
if opt_cmd is None: return 1
|
||
viperlib.snprintf(opt_cmd, opt_cmd_len, "opt -passes=forceattrs,asan -force-attribute=sanitize_address -asan-globals=false -o %s %s", asan_path, compile_input)
|
||
opt_ret: int = stdlib.system(opt_cmd)
|
||
if opt_ret != 0:
|
||
VLogger.error(f"opt ASan 插桩失败 (module={module_name})", "ASAN")
|
||
return 1
|
||
compile_input = asan_path
|
||
|
||
# 构造命令: llc -filetype=obj -o {obj_path} ir_path
|
||
llc_fp2: str = _llc_frame_ptr_flag()
|
||
cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp2) + len(obj_path) + len(compile_input) + 64
|
||
cmd: bytes = _mbuddy.alloc(cmd_len)
|
||
if cmd is None: return 1
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s%s -o %s %s", cc_cmd, cc_flags, llc_fp2, obj_path, compile_input)
|
||
|
||
# 用 CreateProcessA 直启 llc(避开 cmd/c 在 asan 下挂起),子进程继承父控制台:
|
||
# stdout|stderr 直接显示;失败时打印模块 sha1 + 退出码 + 完整命令(退出码同步返回)
|
||
sys_ret: int = _run_cmd_wait(cmd)
|
||
if sys_ret != 0:
|
||
VLogger.error(f"编译失败 (module={module_name}, exit={sys_ret}, cmd={cmd})", "LLC")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# llc 并行编译(线程异步,Phase B 多模块同时跑 llc)
|
||
#
|
||
# 线程安全约束:
|
||
# - LlcTask 及命令串一律 stdlib.malloc:主池 mb 会被 reset_to(mark2) 回收,线程运行期间字符串悬空 → 概率崩溃
|
||
# - worker 只调 _run_cmd_wait(内部仅 stdlib.malloc/CreateProcessA/GetLastError/CloseHandle),不做任何池分配(MemBuddy 分配非线程安全)
|
||
# - 依赖 CreateProcessA + Win32 句柄接口的多线程安全性
|
||
# ============================================================
|
||
# llc 并行最大线程数(参考 TPC Phase2Translator n_workers=os.cpu_count(),本机 16 核)
|
||
define LLC_MAX_THREADS = 16
|
||
|
||
# LlcTask.FailStage:worker 写"失败发生在哪一步"
|
||
# 0 = 未失败/成功;1 = mlir-opt 校验;2 = mlir-translate 降级;3 = ASan opt 插桩;4 = llc
|
||
# 此前 worker 只写 Result/ExitCode,主线程只能按 LlcCmd 打印 ⇒ MLIR 后端下
|
||
# mlir-opt/mlir-translate 失败也被误报成 "llc 编译失败"(现场:.ll 里是 LLVM IR
|
||
# 时 mlir-opt 报 "unexpected character",日志却指向 llc,定位被带偏)。
|
||
define LLC_STAGE_NONE = 0
|
||
define LLC_STAGE_MLIR_OPT = 1
|
||
define LLC_STAGE_MLIR_TRANSLATE = 2
|
||
define LLC_STAGE_ASAN_OPT = 3
|
||
define LLC_STAGE_LLC = 4
|
||
|
||
|
||
class LlcTask:
|
||
"""llc 异步任务:命令串(stdlib.malloc)+ 模块 SHA1 + 结果"""
|
||
OptCmd: bytes # ASan 插桩命令(非 ASan 为 None)
|
||
LlcCmd: bytes # llc 命令
|
||
ModuleSha1: bytes # 模块 SHA1(报错用)
|
||
Result: int # worker 写:0 成功,非 0 失败
|
||
# ExitCode 追加在结尾:既有字段偏移不变;LlcTask.__sizeof__() 随之增大,
|
||
# 所有构造/初始化点均按 __sizeof__() 算首址并对整条 memset,无需改偏移。
|
||
ExitCode: int # worker 写:失败命令退出码(成功为 0)
|
||
# MLIR 后端降级命令(非 MLIR 为 None);同样追加在结尾,不影响既有偏移。
|
||
MlirOptCmd: bytes # mlir-opt 校验命令
|
||
MlirTranslateCmd: bytes # mlir-translate 降级命令
|
||
# 失败阶段(LLC_STAGE_*,见上方常量);追加在结尾,既有偏移不变。
|
||
FailStage: int # worker 写:失败发生在哪一步
|
||
|
||
|
||
def init_llc_task(task: LlcTask*, ir_path: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int:
|
||
"""填充 llc 任务(全部 stdlib.malloc,线程运行期不被回收),0 成功"""
|
||
if task is None or ir_path is None or output_dir is None or module_name is None: return 1
|
||
string.memset(task, 0, LlcTask.__sizeof__())
|
||
task.Result = -1
|
||
obj_path: str = build_sliced_path(output_dir, module_name, "obj")
|
||
if obj_path is None: return 1
|
||
# 模块 SHA1
|
||
sl: sizet = len(module_name)
|
||
task.ModuleSha1 = stdlib.malloc(sl + 1)
|
||
if task.ModuleSha1 is None: return 1
|
||
string.strcpy(task.ModuleSha1, module_name)
|
||
# llc 输入选择:ASan 模式必须喂插桩后的 .asan.ll,与串行路径
|
||
# compile_ll_to_obj 的 compile_input 同口径(否则 opt 生成了插桩产物
|
||
# 却被 llc 忽略,App 侧 obj 里 0 处 __asan,内存排查半盲)
|
||
compile_input: str = ir_path
|
||
# MLIR 降级(单端固定,无开关):worker 先跑 mlir-opt(校验)→ mlir-translate
|
||
# (降级回 LLVM IR),之后的 ASan OptCmd / LlcCmd 输入必须是 <...>.ll.local。
|
||
ll_local_path: str = _ir_sibling_path(ir_path, ".ll.local")
|
||
mlir_opt_out: str = _ir_sibling_path(ir_path, ".mlir.opt")
|
||
if ll_local_path is None or mlir_opt_out is None: return 1
|
||
mo_len: sizet = len(MLIR_OPT_CMD) + len(ir_path) + len(mlir_opt_out) + 16
|
||
task.MlirOptCmd = stdlib.malloc(mo_len)
|
||
if task.MlirOptCmd is None: return 1
|
||
viperlib.snprintf(task.MlirOptCmd, mo_len, "%s %s -o %s", MLIR_OPT_CMD, ir_path, mlir_opt_out)
|
||
mt_len: sizet = len(MLIR_TRANSLATE_CMD) + len(mlir_opt_out) + len(ll_local_path) + 40
|
||
task.MlirTranslateCmd = stdlib.malloc(mt_len)
|
||
if task.MlirTranslateCmd is None: return 1
|
||
viperlib.snprintf(task.MlirTranslateCmd, mt_len, "%s --mlir-to-llvmir %s -o %s", MLIR_TRANSLATE_CMD, mlir_opt_out, ll_local_path)
|
||
compile_input = ll_local_path
|
||
# ASan 模式:opt 插桩命令(worker 先跑 MLIR 降级/opt 再跑 llc,故 LlcCmd 的输入
|
||
# 必须指向 asan_path;失败即置 Result)
|
||
if _asan_mode != 0:
|
||
asan_path: str = build_sliced_path(output_dir, module_name, "asan.ll")
|
||
if asan_path is None: return 1
|
||
opt_len: sizet = len(asan_path) + len(compile_input) + 192
|
||
task.OptCmd = stdlib.malloc(opt_len)
|
||
if task.OptCmd is None: return 1
|
||
viperlib.snprintf(task.OptCmd, opt_len, "opt -passes=forceattrs,asan -force-attribute=sanitize_address" " -asan-globals=false -o %s %s", asan_path, compile_input)
|
||
compile_input = asan_path
|
||
# llc 命令(输入 = compile_input:非 ASan 为 .ll,ASan 为 .asan.ll)
|
||
llc_fp3: str = _llc_frame_ptr_flag()
|
||
cmd_len: sizet = len(cc_cmd) + len(cc_flags) + len(llc_fp3) + len(obj_path) + len(compile_input) + 64
|
||
task.LlcCmd = stdlib.malloc(cmd_len)
|
||
if task.LlcCmd is None: return 1
|
||
viperlib.snprintf(task.LlcCmd, cmd_len, "%s %s%s -o %s %s", cc_cmd, cc_flags, llc_fp3, obj_path, compile_input)
|
||
return 0
|
||
|
||
|
||
def free_llc_task(task: LlcTask*):
|
||
"""释放任务的命令串;记录本身是任务数组的连续槽(非独立 malloc),
|
||
由调用方对数组整体 stdlib.free(此前对槽 free 导致 double-free)"""
|
||
if task is None: return
|
||
if task.OptCmd is not None:
|
||
stdlib.free(task.OptCmd)
|
||
task.OptCmd = None
|
||
if task.LlcCmd is not None:
|
||
stdlib.free(task.LlcCmd)
|
||
task.LlcCmd = None
|
||
if task.MlirOptCmd is not None:
|
||
stdlib.free(task.MlirOptCmd)
|
||
task.MlirOptCmd = None
|
||
if task.MlirTranslateCmd is not None:
|
||
stdlib.free(task.MlirTranslateCmd)
|
||
task.MlirTranslateCmd = None
|
||
if task.ModuleSha1 is not None:
|
||
stdlib.free(task.ModuleSha1)
|
||
task.ModuleSha1 = None
|
||
|
||
|
||
def _phase2_write_cmd(cmd: str) -> None:
|
||
"""--phase2command 模式:把命令追加写入 bat(不执行)
|
||
|
||
首次调用写入 header(@echo off + 说明),后续命令以 \r\n 分行追加,
|
||
与 cmd.exe 批处理格式一致。bat 路径由模块级 _phase2_cmd_path 指定。
|
||
"""
|
||
global _phase2_bat_init
|
||
if _phase2_cmd_path is None or cmd is None: return
|
||
f: rawptr[fileio.File] = fileio.File(_phase2_cmd_path, fileio.MODE.A)
|
||
if f.closed:
|
||
VLogger.error("phase2command: bat 打开失败", "phase2cmd")
|
||
return
|
||
if _phase2_bat_init == 0:
|
||
_phase2_bat_init = 1
|
||
hdr1: str = "@echo off\r\n"
|
||
hdr2: str = "rem TransPyV --phase2command generated commands\r\n"
|
||
f.write(hdr1, string.strlen(hdr1))
|
||
f.write(hdr2, string.strlen(hdr2))
|
||
f.write(cmd, string.strlen(cmd))
|
||
f.write("\r\n", 2)
|
||
f.close()
|
||
|
||
|
||
def _run_cmd_wait(cmd: str) -> int:
|
||
"""CreateProcessA 同步执行命令并等待退出,返回退出码。
|
||
|
||
替代 stdlib.system:Windows asan 环境下 cmd /c 经 CreateProcess 受
|
||
RTL allocator hook 干扰可能挂起(LLVM issue #111166 同类,ClassArrayTest
|
||
llc 固定卡死根因);CreateProcessA 直接执行目标程序并继承父进程控制台
|
||
(stdout/stderr 直接显示,同 system 语义)。全 stdlib.malloc/栈分配,
|
||
worker 线程安全。CreateProcessA 返回 0 时立即取 GetLastError 并以
|
||
VLogger.warning 打印(外部命令未启动)。
|
||
"""
|
||
if cmd is None: return -1
|
||
cl_len: sizet = len(cmd)
|
||
cmd_line: bytes = stdlib.malloc(cl_len + 1)
|
||
if cmd_line is None: return -1
|
||
string.strcpy(cmd_line, cmd)
|
||
sa: w32.win32base.SECURITY_ATTRIBUTES = w32.win32base.SECURITY_ATTRIBUTES()
|
||
si: win32process.STARTUPINFOA = win32process.STARTUPINFOA()
|
||
pi: win32process.PROCESS_INFORMATION = win32process.PROCESS_INFORMATION()
|
||
# T() 构造仅 alloca 不 memset:STARTUPINFOA 未设字段(dwFlags 等)全为
|
||
# 栈垃圾,多线程并发调用时垃圾随机 → dwFlags 垃圾位让 CreateProcessA
|
||
# 用垃圾句柄作子进程 stdio → 子进程行为不定/父进程线程崩溃(ClassArrayTest
|
||
# e5fb worker 崩溃、edd8 正常的原因)。si/sa/pi 全部显式清零。
|
||
string.memset(t.CPtr(&si), 0, win32process.STARTUPINFOA.__sizeof__())
|
||
string.memset(t.CPtr(&sa), 0, w32.win32base.SECURITY_ATTRIBUTES.__sizeof__())
|
||
string.memset(t.CPtr(&pi), 0, win32process.PROCESS_INFORMATION.__sizeof__())
|
||
sa.nLength = w32.win32base.SECURITY_ATTRIBUTES.__sizeof__()
|
||
sa.lpSecurityDescriptor = t.CPtr(0)
|
||
sa.bInheritHandle = 1
|
||
si.cb = win32process.STARTUPINFOA.__sizeof__()
|
||
# create_flags=0:子进程继承父控制台(输出直接显示,同 system 语义);
|
||
# si.dwFlags 保持 memset 后的 0(不置 STARTF_USESTDHANDLES、不设 std 句柄)
|
||
ok: int = win32process.CreateProcessA(t.CPtr(0), cmd_line, t.CPtr(0), t.CPtr(0), 1, 0, t.CPtr(0), t.CPtr(0), t.CPtr(&si), t.CPtr(&pi))
|
||
# CreateProcessA 失败诊断:GetLastError 必须在返回后立即取(此后 stdlib.free/
|
||
# CloseHandle/SetConsoleTextAttribute 等都会覆盖它)。用 warning 不用 error——
|
||
# VLogger.error 会 sys.exit(1),而本函数可能在 worker 线程里执行;warning 只走
|
||
# CRT printf + 控制台着色,不做 MemBuddy 池分配,满足 worker 线程安全约束。
|
||
if ok == 0:
|
||
cp_err: uint32 = w32.win32base.GetLastError()
|
||
VLogger.warning(f"CreateProcessA 失败 (GetLastError={cp_err}) 外部命令未启动: cmd={cmd}", "LLC")
|
||
stdlib.free(cmd_line)
|
||
if ok == 0: return -1
|
||
win32sync.WaitForSingleObject(pi.hProcess, w32.win32base.INFINITE)
|
||
exit_code: uint32 = 0
|
||
win32process.GetExitCodeProcess(pi.hProcess, t.CPtr(&exit_code))
|
||
w32.win32base.CloseHandle(pi.hProcess)
|
||
w32.win32base.CloseHandle(pi.hThread)
|
||
return int(exit_code)
|
||
|
||
|
||
def _llc_task_worker(arg: t.CPtr) -> t.CPtr:
|
||
"""llc 线程入口:只调 _run_cmd_wait(),不做任何池分配(线程安全)
|
||
Result 仅在命令链执行完后写入(初始 -1;join 超时保持 -1 → 报失败);
|
||
ExitCode 记录失败命令的退出码,供主线程打印"""
|
||
task: LlcTask* = (LlcTask*)(arg)
|
||
if task is None: return t.CPtr(0)
|
||
# MLIR 后端:先校验(mlir-opt)→ 降级(mlir-translate),再 ASan opt → llc
|
||
if task.MlirOptCmd is not None:
|
||
mo_rc: int = _run_cmd_wait(task.MlirOptCmd)
|
||
if mo_rc != 0:
|
||
task.ExitCode = mo_rc
|
||
task.FailStage = LLC_STAGE_MLIR_OPT
|
||
task.Result = 1
|
||
return t.CPtr(0)
|
||
if task.MlirTranslateCmd is not None:
|
||
mt_rc: int = _run_cmd_wait(task.MlirTranslateCmd)
|
||
if mt_rc != 0:
|
||
task.ExitCode = mt_rc
|
||
task.FailStage = LLC_STAGE_MLIR_TRANSLATE
|
||
task.Result = 1
|
||
return t.CPtr(0)
|
||
if task.OptCmd is not None:
|
||
opt_rc: int = _run_cmd_wait(task.OptCmd)
|
||
if opt_rc != 0:
|
||
task.ExitCode = opt_rc
|
||
task.FailStage = LLC_STAGE_ASAN_OPT
|
||
task.Result = 1
|
||
return t.CPtr(0)
|
||
llc_rc: int = _run_cmd_wait(task.LlcCmd)
|
||
task.ExitCode = llc_rc
|
||
if llc_rc != 0:
|
||
task.FailStage = LLC_STAGE_LLC
|
||
task.Result = 1
|
||
else:
|
||
task.FailStage = LLC_STAGE_NONE
|
||
task.Result = 0
|
||
return t.CPtr(0)
|
||
|
||
|
||
def run_llc_parallel(tasks: bytes, task_count: int, max_threads: int) -> int:
|
||
"""并行执行 llc:分批 max_threads 个线程,全部 join 后检查结果
|
||
|
||
tasks: LlcTask 记录连续数组(stdlib.malloc),每个记录由 init_llc_task 填充。
|
||
Returns: 0 全部成功;非 0 有失败(已打印失败模块与命令)
|
||
"""
|
||
if tasks is None or task_count <= 0: return 0
|
||
# --phase2command 模式:不 spawn 线程,把 opt(ASan)+ llc 命令全部写入 bat
|
||
if _phase2_cmd_path is not None:
|
||
for pi2 in range(task_count):
|
||
tsk_p: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(pi2) * LlcTask.__sizeof__())
|
||
if tsk_p is None: continue
|
||
if tsk_p.MlirOptCmd is not None: _phase2_write_cmd(tsk_p.MlirOptCmd)
|
||
if tsk_p.MlirTranslateCmd is not None: _phase2_write_cmd(tsk_p.MlirTranslateCmd)
|
||
if tsk_p.OptCmd is not None: _phase2_write_cmd(tsk_p.OptCmd)
|
||
if tsk_p.LlcCmd is not None: _phase2_write_cmd(tsk_p.LlcCmd)
|
||
VLogger.info(f"phase2command 模式:{task_count} 条 llc 命令已写入 {_phase2_cmd_path}", "phase2cmd")
|
||
return 0
|
||
if max_threads <= 0: max_threads = 1
|
||
# 线程句柄数组:list[str](str=bytes=i8*,elem_size=8 显式)存句柄值;list[t.CPtr] 特化下 append 会读零页崩溃(原因待查),list[str] 全库验证稳定。
|
||
# start() 后立即取 handle 存入,join/close 直接对句柄操作,不依赖 Thread 对象存续。
|
||
handles: list[str]* = list[str](_mbuddy, 8)
|
||
if handles is None:
|
||
VLogger.error("run_llc_parallel: 线程句柄数组分配失败", "PhaseB")
|
||
return 1
|
||
slot_size: sizet = LlcTask.__sizeof__()
|
||
ret: int = 0
|
||
# 分批执行:batch_start 按 max_threads 步进(每批 spawn+join+close 一批句柄)。
|
||
for batch_start in range(0, task_count, max_threads):
|
||
batch_n: int = max_threads
|
||
if batch_start + batch_n > task_count: batch_n = task_count - batch_start
|
||
# 本批 spawn
|
||
for j in range(batch_n):
|
||
tsk: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(batch_start + j) * slot_size)
|
||
th: rawptr[vthreading.Thread] = vthreading.Thread(_llc_task_worker, t.CPtr(tsk))
|
||
th.start()
|
||
# start() 后立即取出句柄(Thread 对象栈地址可能被后续构造复用,
|
||
# join 时再读 self.handle 可能已被 close 清空/覆盖)
|
||
handles.append(t.CPtr(th.handle))
|
||
for j in range(batch_n):
|
||
h_j: t.CPtr = t.CPtr(handles.get(batch_start + j))
|
||
wr: uint32 = win32sync.WaitForSingleObject(h_j, 600000)
|
||
if wr != 0: VLogger.error("llc 线程 join 超时/失败(600 秒未完成)", "PhaseB")
|
||
w32.win32base.CloseHandle(h_j)
|
||
# 本批结果检查
|
||
for j in range(batch_n):
|
||
tsk2: LlcTask* = (LlcTask*)(uint64(tasks) + sizet(batch_start + j) * slot_size)
|
||
if tsk2 is not None and tsk2.Result != 0:
|
||
# 失败:按 FailStage 定位到**具体哪一步**并打印对应该步的命令。
|
||
# 此前一律打印 LlcCmd + "llc 编译失败":MLIR 后端下 mlir-opt /
|
||
# mlir-translate 失败(如 .ll 里其实是 LLVM IR 时 mlir-opt 报
|
||
# "unexpected character")被误指到 llc,定位被带偏。
|
||
st: int = tsk2.FailStage
|
||
stage_name: str = "未知阶段(worker 未跑完)"
|
||
stage_cmd: bytes = tsk2.LlcCmd
|
||
if st == LLC_STAGE_MLIR_OPT:
|
||
stage_name = "mlir-opt 校验"
|
||
stage_cmd = tsk2.MlirOptCmd
|
||
elif st == LLC_STAGE_MLIR_TRANSLATE:
|
||
stage_name = "mlir-translate 降级"
|
||
stage_cmd = tsk2.MlirTranslateCmd
|
||
elif st == LLC_STAGE_ASAN_OPT:
|
||
stage_name = "opt ASan 插桩"
|
||
stage_cmd = tsk2.OptCmd
|
||
elif st == LLC_STAGE_LLC:
|
||
stage_name = "llc 编译"
|
||
stage_cmd = tsk2.LlcCmd
|
||
VLogger.error(f"{stage_name}失败: {(tsk2.ModuleSha1 if tsk2.ModuleSha1 is not None else "?")} (exit={tsk2.ExitCode} cmd={(stage_cmd if stage_cmd is not None else "?")})", "LLC")
|
||
ret = 1
|
||
return ret
|
||
|
||
|
||
def _CollectObjFilesImpl(base_dir: str, bd_len: sizet, out_buf: bytes, out_pos: sizet, out_size: sizet) -> sizet:
|
||
"""递归扫描 base_dir,收集 .obj 文件路径到 out_buf,返回新的 out_pos
|
||
|
||
支持切片子目录(如 dir/43/{sha1}.obj)。
|
||
"""
|
||
find_data: w32.win32file.WIN32_FIND_DATAA* = _mbuddy.alloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
||
if find_data is None:
|
||
VLogger.warning("_CollectObjFilesImpl 分配 find_data 失败,少收集 .obj", "collect")
|
||
return out_pos
|
||
string.memset(find_data, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
||
|
||
pattern: bytes = _mbuddy.alloc(bd_len + 8)
|
||
if pattern is None:
|
||
VLogger.warning("_CollectObjFilesImpl 分配 pattern 失败,少收集 .obj", "collect")
|
||
return out_pos
|
||
viperlib.snprintf(pattern, bd_len + 8, "%s/*", base_dir)
|
||
|
||
handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data)
|
||
_mbuddy.free(pattern)
|
||
if handle == w32.win32base.INVALID_HANDLE_VALUE:
|
||
VLogger.warning("_CollectObjFilesImpl 无法打开目录,少收集 .obj", "collect")
|
||
return out_pos
|
||
|
||
while True:
|
||
fname: str = find_data.cFileName
|
||
if fname is not None:
|
||
fname_len: sizet = len(fname)
|
||
if fname_len > 0:
|
||
# 跳过 . 和 ..
|
||
is_dot: int = 0
|
||
if fname_len == 1 and fname[0] == '.': is_dot = 1
|
||
elif fname_len == 2 and fname[0] == '.' and fname[1] == '.': is_dot = 1
|
||
if is_dot == 0:
|
||
attrs: ULONG = find_data.dwFileAttributes
|
||
is_dir: int = 0
|
||
if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: is_dir = 1
|
||
if is_dir != 0:
|
||
# 递归扫描子目录
|
||
sub_dir: bytes = _mbuddy.alloc(bd_len + fname_len + 2)
|
||
if sub_dir is not None:
|
||
viperlib.snprintf(sub_dir, bd_len + fname_len + 2, "%s/%s", base_dir, fname)
|
||
sub_len: sizet = len(sub_dir)
|
||
out_pos = _CollectObjFilesImpl(sub_dir, sub_len, out_buf, out_pos, out_size)
|
||
_mbuddy.free(sub_dir)
|
||
else:
|
||
# 检查是否是 .obj 文件
|
||
if fname_len > 4 and fname[fname_len - 4] == '.' and fname[fname_len - 3] == 'o' and fname[fname_len - 2] == 'b' and fname[fname_len - 1] == 'j':
|
||
need: sizet = bd_len + 1 + fname_len + 2
|
||
if out_pos + need >= out_size: break
|
||
if out_pos > 0:
|
||
out_buf[out_pos] = ' '
|
||
out_pos += 1
|
||
viperlib.snprintf(out_buf + out_pos, need, "%s/%s", base_dir, fname)
|
||
out_pos += bd_len + 1 + fname_len
|
||
|
||
if w32.win32file.FindNextFileA(handle, find_data) == 0: break
|
||
|
||
w32.win32file.FindClose(handle)
|
||
return out_pos
|
||
|
||
|
||
def collect_obj_files(includes_binary_dir: str, out_buf: bytes, out_size: sizet) -> sizet:
|
||
"""递归扫描 includes_binary_dir 目录,收集所有 .obj 文件路径到 out_buf
|
||
|
||
支持切片子目录(如 includes_binary_dir/43/{sha1}.obj)。
|
||
|
||
Args:
|
||
includes_binary_dir: includes.binary 目录路径
|
||
out_buf: 输出缓冲区(用于存放空格分隔的 .obj 文件路径)
|
||
out_size: 输出缓冲区大小
|
||
|
||
Returns:
|
||
写入的字节数(不含 null 终止符),0 表示无文件或错误
|
||
"""
|
||
if includes_binary_dir is None or out_buf is None or out_size == 0: return 0
|
||
|
||
out_buf[0] = '\0'
|
||
dir_len: sizet = len(includes_binary_dir)
|
||
out_pos: sizet = _CollectObjFilesImpl(includes_binary_dir, dir_len, out_buf, 0, out_size)
|
||
out_buf[out_pos] = '\0'
|
||
return out_pos
|
||
|
||
|
||
def _append_host_export_implib(linker_flags: str, implib_path: str) -> str:
|
||
"""宿主导出模式:为链接参数追加 `--out-implib=<绝对路径>`(REPL 片段导入宿主用)
|
||
|
||
触发条件 = linker_flags 显式含 "--export-all-symbols"(工程级开关,宿主的
|
||
project.vpj 里声明)。此时 exe 会带导出表,REPL 片段 DLL 需要一份 import lib
|
||
才能把 IR 里的跨模块引用(形如 `<16位hex>.<函数名>`,见 .text.ll 的
|
||
`define i32 @"<sha1>.utf8_is_valid"`)解析到宿主的导出符号。
|
||
|
||
为什么路径由本层计算而不写进 project.vpj:Config 对 linker.flags 既不做
|
||
%VAR% 展开、也不按 project_dir 解析(Config.vpj:180-230 的 _join_flags_array
|
||
只做拼接;:570-632 只重写 -T 后的脚本路径;env 展开只发生在 static_libs/
|
||
dynamic_libs/includes/build_dir 等字段),而 out-implib 的值是相对
|
||
clang 进程 cwd 解析的 ⇒ 只有本层能从 output_dir + linker_output 算出绝对路径。
|
||
|
||
未开启工程的 linker_flags 不含该标志 ⇒ 原样返回,链接命令逐字节不变。
|
||
"""
|
||
if linker_flags is None or implib_path is None: return linker_flags
|
||
if string.strstr(linker_flags, "--export-all-symbols") is None: return linker_flags
|
||
extra: sizet = string.strlen(" -Wl,--out-implib=") + string.strlen(implib_path) + 1
|
||
buf: str = _mbuddy.alloc(string.strlen(linker_flags) + extra)
|
||
if buf is None:
|
||
VLogger.warning("宿主导出:import lib 参数缓冲分配失败(片段将无法导入宿主符号)", "link")
|
||
return linker_flags
|
||
viperlib.snprintf(buf, string.strlen(linker_flags) + extra, "%s -Wl,--out-implib=%s", linker_flags, implib_path)
|
||
VLogger.info(f"宿主导出:产出 import lib {implib_path}", "link")
|
||
return buf
|
||
|
||
|
||
def link_obj_to_exe(output_dir: str, module_name: str, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> int:
|
||
"""调用 clang++ 链接 .obj 为 .exe
|
||
|
||
Args:
|
||
output_dir: 输出目录(包含 .obj 文件)
|
||
module_name: 模块名(SHA1)
|
||
linker_cmd: 链接器命令(如 "clang++")
|
||
linker_flags: 链接器参数
|
||
linker_output: 输出文件名(如 "test.exe")
|
||
includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj)
|
||
linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None)
|
||
linker_dynamic_libs: 显式动态链接文件(.dll/.so,空格分隔绝对路径,可为 None)
|
||
|
||
Returns:
|
||
0 成功,非 0 失败
|
||
"""
|
||
if output_dir is None or linker_cmd is None or module_name is None: return 1
|
||
|
||
# 构造切片 .obj 路径: output_dir/{sha1前缀}/{module_name}.obj
|
||
obj_path: str = build_sliced_path(output_dir, module_name, "obj")
|
||
if obj_path is None: return 1
|
||
|
||
# 宿主 exe 导出(工程 linker.flags 含 --export-all-symbols 时)同步产出 import lib:
|
||
# {output_dir}/{linker_output}.dll.a —— REPL 片段链接时把它加进命令行即可导入
|
||
# 宿主(同一进程/同一份 includes 代码与数据)。未开启的工程不受影响。
|
||
if linker_output is not None:
|
||
implib_len: sizet = string.strlen(output_dir) + string.strlen(linker_output) + 8
|
||
implib_path: str = _mbuddy.alloc(implib_len)
|
||
if implib_path is not None:
|
||
viperlib.snprintf(implib_path, implib_len, "%s/%s.dll.a", output_dir, linker_output)
|
||
linker_flags = _append_host_export_implib(linker_flags, implib_path)
|
||
|
||
# --traceback: link dbghelp (after the host-export rewrite, so both survive)
|
||
linker_flags = _append_traceback_dbghelp(linker_flags)
|
||
|
||
# 收集 includes.binary 的 .obj 文件路径
|
||
EXTRA_BUF_SIZE: sizet = 32768
|
||
extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE)
|
||
if extra_objs is None: return 1
|
||
extra_len: sizet = 0
|
||
if includes_binary_dir is not None:
|
||
extra_len = collect_obj_files(includes_binary_dir, extra_objs, EXTRA_BUF_SIZE)
|
||
if extra_len > 0: VLogger.info(f"附加 {extra_len} 字节的 includes.binary .obj 文件", "link")
|
||
else: VLogger.warning(f"includes.binary 无 .obj 文件: {includes_binary_dir}", "link")
|
||
|
||
# 构造命令: clang++ {obj_path} extra_objs [static_libs/dynamic_libs] -o {output_dir}/{linker_output} linker_flags
|
||
# 注意: .obj 与显式指定的 .a/.obj/.dll/.so 都必须在 -l 库标志之前,否则链接器无法解析符号依赖。
|
||
# ASan 运行库不由 TPV 硬编码(clang 无 x86_64-w64-windows-gnu 的 ASan 运行库,借用 clang64 会牵进 MSVC 专有指令),
|
||
# 改由 project.vpj 的 linker.static_libs / linker.dynamic_libs 显式声明,main.vp 在 --asan 下用 Config.asan_runtime_declared 校验。
|
||
use_flags: str = linker_flags
|
||
static_libs: str = linker_static_libs
|
||
if static_libs is None: static_libs = ""
|
||
if linker_dynamic_libs is not None and len(linker_dynamic_libs) > 0:
|
||
# 合并 static + dynamic 为单个空格分隔字符串(Viper str += 拼接
|
||
# 会生成非法 add i8* IR,须用 snprintf 构造)
|
||
merged_sl: str = _mbuddy.alloc(len(static_libs) + len(linker_dynamic_libs) + 2)
|
||
if merged_sl is not None:
|
||
if len(static_libs) > 0:
|
||
viperlib.snprintf(merged_sl, len(static_libs) + len(linker_dynamic_libs) + 2, "%s %s", static_libs, linker_dynamic_libs)
|
||
else:
|
||
viperlib.snprintf(merged_sl, len(static_libs) + len(linker_dynamic_libs) + 2, "%s", linker_dynamic_libs)
|
||
static_libs = merged_sl
|
||
sl_len: sizet = len(static_libs)
|
||
cmd_len: sizet = len(linker_cmd) + len(obj_path) + len(use_flags) + len(output_dir) + len(linker_output) + extra_len + sl_len + 128
|
||
cmd: bytes = _mbuddy.alloc(cmd_len)
|
||
if cmd is None: return 1
|
||
if sl_len > 0 and extra_len > 0:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s %s %s -o %s/%s %s", linker_cmd, obj_path, extra_objs, static_libs, output_dir, linker_output, use_flags)
|
||
elif sl_len > 0:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s/%s %s", linker_cmd, obj_path, static_libs, output_dir, linker_output, use_flags)
|
||
elif extra_len > 0:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s/%s %s", linker_cmd, obj_path, extra_objs, output_dir, linker_output, use_flags)
|
||
else:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s -o %s/%s %s", linker_cmd, obj_path, output_dir, linker_output, use_flags)
|
||
|
||
# --phase2command 模式:不执行链接,命令写入 bat
|
||
if _phase2_cmd_path is not None:
|
||
_phase2_write_cmd(cmd)
|
||
return 0
|
||
|
||
result: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True)
|
||
if result is None:
|
||
VLogger.error("subprocess.run 返回 None", "link")
|
||
return 1
|
||
if result.returncode != 0:
|
||
VLogger.error(f"链接失败,返回码: {result.returncode}", "link")
|
||
VLogger.error(f"命令: {cmd}", "link")
|
||
# 显示链接器错误输出
|
||
# 注意: subprocess 在 Windows 下将 stderr 合并到 stdout,result.stderr 总是 None
|
||
if result.stdout is not None: VLogger.error(result.stdout, "link")
|
||
else: VLogger.error("无输出捕获", "link")
|
||
if result.stderr is not None: VLogger.error(result.stderr, "link")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def asm_to_obj(asm_path: str, sha1_val: str, output_dir: str, triple: str) -> int:
|
||
"""用 llvm-mc 汇编 .s 源文件为 .obj(切片路径)
|
||
|
||
裸机项目(VKernel 等)含 .s 汇编文件(如 Kernel/intr/isr.s 定义
|
||
isr0/syscall_entry/_per_cpu 等符号)。.s 不经过 TPV 翻译/llc,
|
||
直接用配套的 llvm-mc 汇编为 .obj,obj 路径与其他模块一致
|
||
(output_dir/{sha1前缀}/{sha1}.obj),参与 Phase C 链接。
|
||
|
||
Args:
|
||
asm_path: .s 源文件完整路径
|
||
sha1_val: 文件内容 SHA1 前16字符(切片路径用)
|
||
output_dir: 输出目录
|
||
triple: 目标 triple(如 x86_64-none-elf)
|
||
|
||
Returns:
|
||
0 成功,非 0 失败
|
||
"""
|
||
if asm_path is None or sha1_val is None or output_dir is None: return 1
|
||
obj_path: str = build_sliced_path(output_dir, sha1_val, "obj")
|
||
if obj_path is None: return 1
|
||
trip: str = triple
|
||
if trip is None or trip[0] == '\0': trip = "x86_64-none-elf"
|
||
cmd_len: sizet = len("llvm-mc -triple=") + len(trip) + len(" -filetype=obj -o ") + len(obj_path) + len(asm_path) + 64
|
||
cmd: bytes = _mbuddy.alloc(cmd_len)
|
||
if cmd is None: return 1
|
||
viperlib.snprintf(cmd, cmd_len, "llvm-mc -triple=%s -filetype=obj -o %s %s", trip, obj_path, asm_path)
|
||
|
||
# --phase2command 模式:命令写入 bat(与 llc/链接一致)
|
||
if _phase2_cmd_path is not None:
|
||
_phase2_write_cmd(cmd)
|
||
return 0
|
||
|
||
result: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True)
|
||
if result is None:
|
||
VLogger.error("llvm-mc subprocess.run 返回 None", "asm")
|
||
return 1
|
||
if result.returncode != 0:
|
||
if result.stdout is not None: stdio.printf("%s\n", result.stdout)
|
||
if result.stderr is not None: stdio.printf("%s\n", result.stderr)
|
||
VLogger.error(f"llvm-mc 汇编失败,返回码: {result.returncode}, 命令: {cmd}", "asm")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def compile_module_to_obj(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str) -> int:
|
||
"""编译 IR 到 .obj(不链接)
|
||
|
||
Args:
|
||
ir_buf: LLVM IR 文本缓冲区
|
||
ir_len: IR 文本长度
|
||
temp_dir: 临时目录
|
||
output_dir: 输出目录
|
||
module_name: 模块名(SHA1)
|
||
cc_cmd: 编译器命令
|
||
cc_flags: 编译器参数
|
||
|
||
Returns:
|
||
0 成功,非 0 失败
|
||
"""
|
||
if ir_buf is None or temp_dir is None or output_dir is None or module_name is None: return 1
|
||
|
||
# Step 1: 写 .ll 文件
|
||
ret: int = write_ir_to_file(ir_buf, ir_len, temp_dir, module_name)
|
||
if ret != 0:
|
||
VLogger.error(f"写 .ll 失败: {module_name}", "compile")
|
||
return 1
|
||
|
||
# Step 2: 构造切片 .ll 路径并编译 → .obj
|
||
ir_path: str = build_sliced_path(temp_dir, module_name, "ll")
|
||
if ir_path is None: return 1
|
||
|
||
ret = compile_ll_to_obj(ir_path, output_dir, module_name, cc_cmd, cc_flags)
|
||
if ret != 0:
|
||
VLogger.error(f"llc 编译失败: {module_name}", "compile")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> int:
|
||
"""链接多个 .obj 文件为 .exe
|
||
|
||
Args:
|
||
obj_paths: 空格分隔的 .obj 文件完整路径字符串
|
||
obj_paths_len: obj_paths 长度
|
||
linker_cmd: 链接器命令(如 "clang++")
|
||
linker_flags: 链接器参数
|
||
linker_output: 输出文件完整路径
|
||
includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj)
|
||
linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None)
|
||
|
||
Returns:
|
||
0 成功,非 0 失败
|
||
"""
|
||
if obj_paths is None or obj_paths_len == 0 or linker_cmd is None: return 1
|
||
|
||
# 去重 .obj 路径:同一模块的 obj 可能被多个收集环节加入(Phase B+ 新编译的 obj_paths 与 Phase C reachable 收集指向同一文件);
|
||
# 同一路径在链接命令中出现两次即重复符号(无 --allow-multiple-definition 的工程会 ld.lld 报错),故保留首次出现、后续跳过。
|
||
if obj_paths_len > 0:
|
||
dedup: bytes = _mbuddy.alloc(obj_paths_len + 1)
|
||
if dedup is not None:
|
||
dedup[0] = '\0'
|
||
dpos: sizet = 0
|
||
dsrc_i: sizet = 0
|
||
while dsrc_i < obj_paths_len:
|
||
# 跳过前导空格
|
||
while dsrc_i < obj_paths_len and obj_paths[dsrc_i] == ' ': dsrc_i += 1
|
||
if dsrc_i >= obj_paths_len: break
|
||
dstart: sizet = dsrc_i
|
||
while dsrc_i < obj_paths_len and obj_paths[dsrc_i] != ' ': dsrc_i += 1
|
||
dtlen: sizet = dsrc_i - dstart
|
||
# 检查 dedup 中是否已有相同 token
|
||
ddup: int = 0
|
||
dj: sizet = 0
|
||
while dj < dpos:
|
||
while dj < dpos and dedup[dj] == ' ': dj += 1
|
||
if dj >= dpos: break
|
||
ds2: sizet = dj
|
||
while dj < dpos and dedup[dj] != ' ': dj += 1
|
||
dt2len: sizet = dj - ds2
|
||
if dt2len == dtlen:
|
||
deq: int = 1
|
||
dk: sizet = 0
|
||
while dk < dtlen:
|
||
if dedup[ds2 + dk] != obj_paths[dstart + dk]:
|
||
deq = 0
|
||
break
|
||
dk += 1
|
||
if deq != 0:
|
||
ddup = 1
|
||
break
|
||
if ddup == 0:
|
||
if dpos > 0:
|
||
dedup[dpos] = ' '
|
||
dpos += 1
|
||
string.memcpy(dedup + dpos, obj_paths + dstart, dtlen)
|
||
dpos += dtlen
|
||
dedup[dpos] = '\0'
|
||
if dpos > 0 and dpos != obj_paths_len:
|
||
obj_paths = dedup
|
||
obj_paths_len = dpos
|
||
|
||
# 收集 includes.binary 的 .obj 文件路径
|
||
EXTRA_BUF_SIZE: sizet = 32768
|
||
extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE)
|
||
if extra_objs is None: return 1
|
||
extra_len: sizet = 0
|
||
if includes_binary_dir is not None: extra_len = collect_obj_files(includes_binary_dir, extra_objs, EXTRA_BUF_SIZE)
|
||
|
||
# 构造命令: clang++ obj_paths extra_objs -o linker_output linker_flags
|
||
# ASan 运行库同样不再由 TPV 注入(在 link_obj_to_exe 里已有说明):
|
||
# 由 project.vpj 声明,--asan 下由 main.vp 校验。
|
||
# 宿主导出:linker_output 是完整输出路径 ⇒ import lib 取名 <linker_output>.dll.a
|
||
if linker_output is not None:
|
||
implib_len2: sizet = string.strlen(linker_output) + 8
|
||
implib_path2: str = _mbuddy.alloc(implib_len2)
|
||
if implib_path2 is not None:
|
||
viperlib.snprintf(implib_path2, implib_len2, "%s.dll.a", linker_output)
|
||
linker_flags = _append_host_export_implib(linker_flags, implib_path2)
|
||
# --traceback: link dbghelp (after the host-export rewrite)
|
||
linker_flags = _append_traceback_dbghelp(linker_flags)
|
||
use_flags: str = linker_flags
|
||
static_libs2: str = linker_static_libs
|
||
if static_libs2 is None: static_libs2 = ""
|
||
if linker_dynamic_libs is not None and len(linker_dynamic_libs) > 0:
|
||
# 合并 static + dynamic(Viper str += 拼接生成非法 IR,用 snprintf)
|
||
merged_sl2: str = _mbuddy.alloc(len(static_libs2) + len(linker_dynamic_libs) + 2)
|
||
if merged_sl2 is not None:
|
||
if len(static_libs2) > 0:
|
||
viperlib.snprintf(merged_sl2, len(static_libs2) + len(linker_dynamic_libs) + 2, "%s %s", static_libs2, linker_dynamic_libs)
|
||
else:
|
||
viperlib.snprintf(merged_sl2, len(static_libs2) + len(linker_dynamic_libs) + 2, "%s", linker_dynamic_libs)
|
||
static_libs2 = merged_sl2
|
||
sl2_len: sizet = len(static_libs2)
|
||
cmd_len: sizet = len(linker_cmd) + obj_paths_len + len(use_flags) + len(linker_output) + extra_len + sl2_len + 128
|
||
cmd: bytes = _mbuddy.alloc(cmd_len)
|
||
if cmd is None: return 1
|
||
# linker.static_libs 显式静态链接文件位于 .obj 之后、-o 之前
|
||
if sl2_len > 0 and extra_len > 0:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s %s %s -o %s %s", linker_cmd, obj_paths, extra_objs, static_libs2, linker_output, use_flags)
|
||
elif sl2_len > 0:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s %s", linker_cmd, obj_paths, static_libs2, linker_output, use_flags)
|
||
elif extra_len > 0:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s %s -o %s %s", linker_cmd, obj_paths, extra_objs, linker_output, use_flags)
|
||
else:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s -o %s %s", linker_cmd, obj_paths, linker_output, use_flags)
|
||
|
||
# 用 subprocess.run 捕获 clang++ 的 stdout/stderr 输出
|
||
# 之前用 stdlib.system() 无法捕获具体错误(undefined reference 等),
|
||
# 导致 1.txt 中只看到"链接失败,返回码: 1"而看不到未解析符号
|
||
# --phase2command 模式:不执行链接,命令写入 bat
|
||
if _phase2_cmd_path is not None:
|
||
_phase2_write_cmd(cmd)
|
||
return 0
|
||
result: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True)
|
||
if result is None:
|
||
VLogger.error("subprocess.run 返回 None", "link")
|
||
return 1
|
||
if result.returncode != 0:
|
||
# 先用 stdio.printf 直接输出 clang++ 的完整错误输出(含 undefined reference)
|
||
# 必须在 VLogger.error 之前输出,因为 VLogger.error 会调用 sys.exit(1) 终止进程
|
||
if result.stdout is not None: stdio.printf("=== clang++ 链接错误输出 ===\n%s\n", result.stdout)
|
||
if result.stderr is not None:
|
||
stdio.printf("=== clang++ stderr ===\n%s\n", result.stderr)
|
||
VLogger.error(f"链接失败,返回码: {result.returncode}, 命令: {cmd}", "link")
|
||
return 1
|
||
return 0
|
||
|
||
|
||
# ============================================================
|
||
# --traceback: <exe>.stackmap sidecar writer (address -> source line)
|
||
#
|
||
# Ported from TPC Phase2Translator._WriteStackMap. Runs after a successful
|
||
# link: llvm-dwarfdump parses the final PE's .debug_line/.debug_info, every
|
||
# address is normalised to a runtime RVA (addr - ImageBase) and the result is
|
||
# written as <exe>.stackmap for the runtime traceback.vp handler.
|
||
#
|
||
# Layout (all little-endian):
|
||
# header 20B = "TPCS" | u32 version=1 | u32 entry_count | u32 str_count | u32 str_bytes
|
||
# entries = entry_count * 20B, strictly ascending rva:
|
||
# u64 rva | u32 file_off | u32 line | u32 func_off
|
||
# pool = str_bytes UTF-8 bytes, '\0'-terminated strings, offset 0 is an
|
||
# empty-string sentinel, func_off==0 means "no function name"
|
||
#
|
||
# Every failure aborts the build (VLogger.error exits); there is no fallback.
|
||
# ============================================================
|
||
|
||
# row record: u64 rva | u64 file_path | u32 line | u32 pad | u64 func_name(可空)
|
||
# func_name 仅我方 .vpcall 派生行携带(objdump 标签名);DWARF 行写 0,序列化时
|
||
# 回退到 DWARF 子程序表查名。
|
||
define TB_ROW_SIZE = 32
|
||
define TB_ROW_OFF_FILE = 8
|
||
define TB_ROW_OFF_LINE = 16
|
||
define TB_ROW_OFF_FUNC = 24
|
||
# func record: u64 low_rva | u64 high_rva | u64 name
|
||
define TB_FUNC_SIZE = 24
|
||
# dir record: u32 idx | u32 pad | u64 name
|
||
define TB_DIR_SIZE = 16
|
||
# file record: u32 idx | u32 dir_index | u64 name | u64 resolved_path
|
||
define TB_FILE_SIZE = 32
|
||
# initial capacities for the (growable) row / func tables
|
||
define TB_ROW_CAP0 = 65536
|
||
define TB_FUNC_CAP0 = 65536
|
||
# fixed per-CU table capacities (exceeding either aborts the build)
|
||
define TB_DIR_CAP = 4096
|
||
define TB_FILE_CAP = 65536
|
||
|
||
|
||
class TbPeLayout:
|
||
Ok: int
|
||
ImageBase: uint64
|
||
SizeOfImage: uint64
|
||
TextRva: uint64
|
||
|
||
def __new__(self) -> t.CPtr:
|
||
return t.CPtr(_mbuddy.alloc(TbPeLayout.__sizeof__()))
|
||
|
||
def __init__(self):
|
||
self.Ok = 0
|
||
self.ImageBase = 0
|
||
self.SizeOfImage = 0
|
||
self.TextRva = 0
|
||
|
||
|
||
class TbDump:
|
||
RowBuf: bytes
|
||
RowCap: int
|
||
RowCount: int
|
||
RawRowCount: int
|
||
FuncBuf: bytes
|
||
FuncCap: int
|
||
FuncCount: int
|
||
|
||
def __new__(self) -> t.CPtr:
|
||
return t.CPtr(_mbuddy.alloc(TbDump.__sizeof__()))
|
||
|
||
def __init__(self):
|
||
self.RowBuf = None
|
||
self.RowCap = 0
|
||
self.RowCount = 0
|
||
self.RawRowCount = 0
|
||
self.FuncBuf = None
|
||
self.FuncCap = 0
|
||
self.FuncCount = 0
|
||
|
||
|
||
class TbPool:
|
||
Pool: bytes
|
||
PoolLen: int
|
||
Offs: bytes
|
||
OffCount: int
|
||
|
||
def __new__(self) -> t.CPtr:
|
||
return t.CPtr(_mbuddy.alloc(TbPool.__sizeof__()))
|
||
|
||
def __init__(self):
|
||
self.Pool = None
|
||
self.PoolLen = 0
|
||
self.Offs = None
|
||
self.OffCount = 0
|
||
|
||
|
||
# ---- little-endian byte helpers (no struct.pack in Viper) ----
|
||
def _tb_u8(buf: bytes, off: sizet) -> uint32:
|
||
return uint32(int(buf[off]) & 0xFF)
|
||
|
||
|
||
def _tb_rd_u16(buf: bytes, off: sizet) -> uint32:
|
||
return _tb_u8(buf, off) | (_tb_u8(buf, off + 1) << 8)
|
||
|
||
|
||
def _tb_rd_u32(buf: bytes, off: sizet) -> uint32:
|
||
b0: uint32 = _tb_u8(buf, off)
|
||
b1: uint32 = _tb_u8(buf, off + 1)
|
||
b2: uint32 = _tb_u8(buf, off + 2)
|
||
b3: uint32 = _tb_u8(buf, off + 3)
|
||
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
|
||
|
||
|
||
def _tb_rd_u64(buf: bytes, off: sizet) -> uint64:
|
||
lo: uint64 = uint64(_tb_rd_u32(buf, off))
|
||
hi: uint64 = uint64(_tb_rd_u32(buf, off + 4))
|
||
return lo | (hi << 32)
|
||
|
||
|
||
def _tb_wr_u32(buf: bytes, off: sizet, v: uint64):
|
||
buf[off] = char(int(v & 0xFF))
|
||
buf[off + 1] = char(int((v >> 8) & 0xFF))
|
||
buf[off + 2] = char(int((v >> 16) & 0xFF))
|
||
buf[off + 3] = char(int((v >> 24) & 0xFF))
|
||
|
||
|
||
def _tb_wr_u64(buf: bytes, off: sizet, v: uint64):
|
||
_tb_wr_u32(buf, off, v & 0xFFFFFFFF)
|
||
_tb_wr_u32(buf, off + 4, (v >> 32) & 0xFFFFFFFF)
|
||
|
||
|
||
# ---- tiny string helpers (no re module) ----
|
||
def _tb_cc(s: str, i: sizet) -> int:
|
||
"""Byte value at s[i] as 0..255 (chars in DWARF text are ASCII)."""
|
||
if s is None: return 0
|
||
return int(s[i]) & 0xFF
|
||
|
||
|
||
def _tb_contains(s: str, sub: str) -> int:
|
||
if s is None or sub is None: return 0
|
||
return 1 if string.strstr(s, sub) is not None else 0
|
||
|
||
|
||
def _tb_startswith(s: str, pfx: str) -> int:
|
||
if s is None or pfx is None: return 0
|
||
pl: sizet = string.strlen(pfx)
|
||
if string.strlen(s) < pl: return 0
|
||
return 1 if string.strncmp(s, pfx, pl) == 0 else 0
|
||
|
||
|
||
def _tb_ltrim(s: str) -> str:
|
||
if s is None: return None
|
||
i: sizet = 0
|
||
while _tb_cc(s, i) == 32 or _tb_cc(s, i) == 9: i += 1
|
||
return s + i
|
||
|
||
|
||
def _tb_len_trimmed(s: str) -> sizet:
|
||
if s is None: return 0
|
||
n: sizet = string.strlen(s)
|
||
while n > 0:
|
||
c: int = _tb_cc(s, n - 1)
|
||
if c == 32 or c == 9 or c == 13: n -= 1
|
||
else: break
|
||
return n
|
||
|
||
|
||
def _tb_dup_span(mb: memhub.MemBuddy*, src: str, n: sizet) -> str:
|
||
"""Copy src[0..n) into a fresh NUL-terminated string."""
|
||
if src is None: return None
|
||
out: str = mb.alloc(n + 1)
|
||
if out is None: return None
|
||
string.strncpy(out, src, n)
|
||
out[n] = '\0'
|
||
return out
|
||
|
||
|
||
def _tb_unquote(mb: memhub.MemBuddy*, v: str) -> str:
|
||
"""Trim trailing spaces and strip one pair of surrounding quotes (TPC _Unquote)."""
|
||
if v is None: return None
|
||
n: sizet = _tb_len_trimmed(v)
|
||
if n >= 2 and _tb_cc(v, 0) == 34 and _tb_cc(v, n - 1) == 34: return _tb_dup_span(mb, v + 1, n - 2)
|
||
return _tb_dup_span(mb, v, n)
|
||
|
||
|
||
def _tb_is_abs_path(s: str) -> int:
|
||
if s is None: return 0
|
||
c0: int = _tb_cc(s, 0)
|
||
if c0 == 0: return 0
|
||
if c0 == 47 or c0 == 92: return 1
|
||
if _tb_cc(s, 1) == 58: return 1
|
||
return 0
|
||
|
||
|
||
def _tb_join(mb: memhub.MemBuddy*, base: str, name: str) -> str:
|
||
"""Join base and name with a separator unless base already ends in one."""
|
||
if base is None or string.strlen(base) == 0: return name
|
||
if name is None: return base
|
||
bl: sizet = string.strlen(base)
|
||
nl: sizet = string.strlen(name)
|
||
buf: str = mb.alloc(bl + nl + 2)
|
||
if buf is None: return name
|
||
last: int = _tb_cc(base, bl - 1)
|
||
if last == 47 or last == 92: viperlib.snprintf(buf, bl + nl + 2, "%s%s", base, name)
|
||
else: viperlib.snprintf(buf, bl + nl + 2, "%s/%s", base, name)
|
||
return buf
|
||
|
||
|
||
def _tb_parse_dec(s: str) -> int:
|
||
"""Parse a leading decimal integer, skipping leading blanks.
|
||
|
||
dwarfdump right-aligns the bracketed indices to a 3-wide field
|
||
(`file_names[ 1]:` / `include_directories[ 12]`), so `_tb_bracket_idx`
|
||
hands us a string whose first char is a space. Without the blank skip the
|
||
digit loop never runs and every index parses as 0 -> every file record's
|
||
pidx becomes 0 while a row's fidx is the real 1..N fit -> `_tb_file_rec`
|
||
never matches and EVERY row degrades to the "??" placeholder. Measured on
|
||
Test/LLvmLiteTest: all 9146 stackmap records carried the single '??' file
|
||
string while function names and line numbers were correct.
|
||
"""
|
||
v: int = 0
|
||
i: sizet = 0
|
||
while _tb_cc(s, i) == 32 or _tb_cc(s, i) == 9: i += 1
|
||
while _tb_cc(s, i) >= 48 and _tb_cc(s, i) <= 57:
|
||
v = v * 10 + (_tb_cc(s, i) - 48)
|
||
i += 1
|
||
return v
|
||
|
||
|
||
def _tb_bracket_idx(s: str) -> int:
|
||
"""Parse the N in '[N]' (first bracket group of the line)."""
|
||
i: sizet = 0
|
||
while _tb_cc(s, i) != 0:
|
||
if _tb_cc(s, i) == 91: return _tb_parse_dec(s + i + 1)
|
||
i += 1
|
||
return -1
|
||
|
||
|
||
def _tb_after_bracket(s: str) -> str:
|
||
i: sizet = 0
|
||
while _tb_cc(s, i) != 0:
|
||
if _tb_cc(s, i) == 93: return s + i + 1
|
||
i += 1
|
||
return None
|
||
|
||
|
||
def _tb_value_after_sep(s: str) -> str:
|
||
"""Return the text after the first '=' or ':' (left-trimmed), or None."""
|
||
if s is None: return None
|
||
i: sizet = 0
|
||
while _tb_cc(s, i) != 0:
|
||
c: int = _tb_cc(s, i)
|
||
if c == 61 or c == 58: return _tb_ltrim(s + i + 1)
|
||
i += 1
|
||
return None
|
||
|
||
|
||
def _tb_hex_digit(ch: int) -> int:
|
||
if ch >= 48 and ch <= 57: return ch - 48
|
||
if ch >= 97 and ch <= 102: return ch - 87
|
||
if ch >= 65 and ch <= 70: return ch - 55
|
||
return -1
|
||
|
||
|
||
def _tb_hex_in_parens(s: str, key: str) -> uint64:
|
||
"""Parse the 0x... value inside the parentheses right after key (DW_AT_low_pc (0x...)).
|
||
|
||
Returns 0 when the strict "( 0x" form is absent; DWARF prints the address form
|
||
that way, and a 0 result is harmless (out of the image VA range -> dropped).
|
||
"""
|
||
p: str = string.strstr(s, key)
|
||
if p is None: return 0
|
||
p = p + string.strlen(key)
|
||
while _tb_cc(p, 0) == 32 or _tb_cc(p, 0) == 9: p = p + 1
|
||
if _tb_cc(p, 0) != 40: return 0
|
||
p = p + 1
|
||
while _tb_cc(p, 0) == 32: p = p + 1
|
||
if _tb_cc(p, 0) != 48 or _tb_cc(p, 1) != 120: return 0
|
||
q: str = p + 2
|
||
v: uint64 = 0
|
||
i: sizet = 0
|
||
hd: int = _tb_hex_digit(_tb_cc(q, 0))
|
||
while hd >= 0:
|
||
v = (v << 4) | uint64(hd)
|
||
i += 1
|
||
hd = _tb_hex_digit(_tb_cc(q, i))
|
||
return v
|
||
|
||
|
||
def _tb_quoted_after(mb: memhub.MemBuddy*, s: str, key: str) -> str:
|
||
"""Extract the first double-quoted string following key (DW_AT_name ("..."))."""
|
||
p: str = string.strstr(s, key)
|
||
if p is None: return None
|
||
p = p + string.strlen(key)
|
||
q: str = string.strchr(p, 40)
|
||
if q is None: return None
|
||
q = q + 1
|
||
while _tb_cc(q, 0) == 32: q = q + 1
|
||
if _tb_cc(q, 0) != 34: return None
|
||
q = q + 1
|
||
start: str = q
|
||
n: sizet = 0
|
||
while _tb_cc(q, n) != 0:
|
||
c: int = _tb_cc(q, n)
|
||
if c == 92:
|
||
if _tb_cc(q, n + 1) == 0: break
|
||
n += 2
|
||
continue
|
||
if c == 34: break
|
||
n += 1
|
||
return _tb_dup_span(mb, start, n)
|
||
|
||
|
||
def _tb_dir_lookup(dirs: bytes, count: int, idx: int) -> str:
|
||
i: int = 0
|
||
while i < count:
|
||
if int(_tb_rd_u32(dirs, sizet(i) * TB_DIR_SIZE)) == idx: return rawptr(_tb_rd_u64(dirs, sizet(i) * TB_DIR_SIZE + 8))
|
||
i += 1
|
||
return None
|
||
|
||
|
||
def _tb_file_rec(files: bytes, count: int, idx: int) -> int:
|
||
i: int = 0
|
||
while i < count:
|
||
if int(_tb_rd_u32(files, sizet(i) * TB_FILE_SIZE)) == idx: return i
|
||
i += 1
|
||
return -1
|
||
|
||
|
||
def _tb_resolve_file(mb: memhub.MemBuddy*, files: bytes, fcount: int, dirs: bytes, dcount: int, frec: int) -> str:
|
||
"""Resolve a file-table entry to a path (absolute name wins, else dir join)."""
|
||
roff: sizet = sizet(frec) * TB_FILE_SIZE
|
||
cached: uint64 = _tb_rd_u64(files, roff + 24)
|
||
if cached != 0: return rawptr(cached)
|
||
name: str = rawptr(_tb_rd_u64(files, roff + 8))
|
||
path: str = name
|
||
if name is not None and _tb_is_abs_path(name) == 0:
|
||
didx: int = int(_tb_rd_u32(files, roff + 4))
|
||
base: str = _tb_dir_lookup(dirs, dcount, didx)
|
||
if base is not None and string.strlen(base) > 0:
|
||
if _tb_is_abs_path(base) == 0:
|
||
root: str = _tb_dir_lookup(dirs, dcount, 0)
|
||
if root is not None and _tb_is_abs_path(root) != 0: base = _tb_join(mb, root, base)
|
||
path = _tb_join(mb, base, name)
|
||
if path is None or string.strlen(path) == 0: path = "??"
|
||
_tb_wr_u64(files, roff + 24, uint64(path))
|
||
return path
|
||
|
||
|
||
def _tb_grow_rec(mb: memhub.MemBuddy*, buf: bytes, old_cap: int, rec_size: int, new_cap: int) -> bytes:
|
||
nb: bytes = mb.alloc(sizet(new_cap) * sizet(rec_size))
|
||
if nb is None: return None
|
||
string.memcpy(nb, buf, sizet(old_cap) * sizet(rec_size))
|
||
mb.free(buf)
|
||
return nb
|
||
|
||
|
||
def _tb_emit_row(mb: memhub.MemBuddy*, d: TbDump*, addr: uint64, path: str, line: int, fname: str = None) -> int:
|
||
if d is None: return 1
|
||
if d.RowCount >= d.RowCap:
|
||
ncap: int = d.RowCap * 2
|
||
nb: bytes = _tb_grow_rec(mb, d.RowBuf, d.RowCap, TB_ROW_SIZE, ncap)
|
||
if nb is None:
|
||
VLogger.error("--traceback: DWARF 行记录表扩容失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
d.RowBuf = nb
|
||
d.RowCap = ncap
|
||
off: sizet = sizet(d.RowCount) * TB_ROW_SIZE
|
||
_tb_wr_u64(d.RowBuf, off, addr)
|
||
_tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FILE, uint64(path))
|
||
_tb_wr_u32(d.RowBuf, off + TB_ROW_OFF_LINE, uint64(line))
|
||
fv: uint64 = 0
|
||
if fname is not None: fv = uint64(fname)
|
||
_tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FUNC, fv)
|
||
d.RowCount = d.RowCount + 1
|
||
d.RawRowCount = d.RawRowCount + 1
|
||
return 0
|
||
|
||
|
||
def _tb_emit_func(mb: memhub.MemBuddy*, d: TbDump*, low: uint64, high: uint64, name: str) -> int:
|
||
if d is None: return 1
|
||
if d.FuncCount >= d.FuncCap:
|
||
ncap: int = d.FuncCap * 2
|
||
nb: bytes = _tb_grow_rec(mb, d.FuncBuf, d.FuncCap, TB_FUNC_SIZE, ncap)
|
||
if nb is None:
|
||
VLogger.error("--traceback: DWARF 子程序表扩容失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
d.FuncBuf = nb
|
||
d.FuncCap = ncap
|
||
off: sizet = sizet(d.FuncCount) * TB_FUNC_SIZE
|
||
_tb_wr_u64(d.FuncBuf, off, low)
|
||
_tb_wr_u64(d.FuncBuf, off + 8, high)
|
||
_tb_wr_u64(d.FuncBuf, off + 16, uint64(name))
|
||
d.FuncCount = d.FuncCount + 1
|
||
return 0
|
||
|
||
|
||
def _tb_func_name_for(fbuf: bytes, fcount: int, rva: uint64) -> str:
|
||
"""bisect_right over the (sorted) func low_pc table, then range check."""
|
||
if fbuf is None or fcount <= 0: return None
|
||
lo: int = 0
|
||
hi: int = fcount
|
||
while lo < hi:
|
||
mid: int = lo + (hi - lo) / 2
|
||
if _tb_rd_u64(fbuf, sizet(mid) * TB_FUNC_SIZE) <= rva: lo = mid + 1
|
||
else: hi = mid
|
||
k: int = lo - 1
|
||
if k < 0: return None
|
||
low: uint64 = _tb_rd_u64(fbuf, sizet(k) * TB_FUNC_SIZE)
|
||
high: uint64 = _tb_rd_u64(fbuf, sizet(k) * TB_FUNC_SIZE + 8)
|
||
if low <= rva and rva < high: return rawptr(_tb_rd_u64(fbuf, sizet(k) * TB_FUNC_SIZE + 16))
|
||
return None
|
||
|
||
|
||
def _tb_intern(p: TbPool*, s: str) -> uint64:
|
||
"""Intern s into the pool (linear scan; keeps offset 0 as the empty sentinel)."""
|
||
if p is None or s is None: return 0
|
||
k: int = 0
|
||
while k < p.OffCount:
|
||
o: uint64 = _tb_rd_u64(p.Offs, sizet(k) * 8)
|
||
if string.strcmp(p.Pool + sizet(o), s) == 0: return o
|
||
k += 1
|
||
off: uint64 = uint64(p.PoolLen)
|
||
n: sizet = string.strlen(s)
|
||
string.memcpy(p.Pool + sizet(off), s, n)
|
||
p.Pool[sizet(off) + n] = '\0'
|
||
p.PoolLen = p.PoolLen + int(n) + 1
|
||
_tb_wr_u64(p.Offs, sizet(p.OffCount) * 8, off)
|
||
p.OffCount = p.OffCount + 1
|
||
return off
|
||
|
||
|
||
def _tb_read_pe_layout(mb: memhub.MemBuddy*, exe_path: str) -> TbPeLayout*:
|
||
"""Read (ImageBase, SizeOfImage, .text RVA) from the PE headers."""
|
||
lay: TbPeLayout* = TbPeLayout()
|
||
if lay is None: return None
|
||
f: rawptr[fileio.File] = fileio.File(exe_path, fileio.MODE.R)
|
||
if f.closed: return lay
|
||
HEAD: sizet = 4096
|
||
head: bytes = mb.alloc(HEAD)
|
||
if head is None:
|
||
f.close()
|
||
return lay
|
||
got: LONG = f.read_all(head, HEAD)
|
||
f.close()
|
||
if got < 0x40: return lay
|
||
if _tb_cc(head, 0) != 77 or _tb_cc(head, 1) != 90: return lay
|
||
e_lfanew: uint64 = uint64(_tb_rd_u32(head, 0x3C))
|
||
if e_lfanew + 24 > uint64(got): return lay
|
||
if _tb_cc(head, sizet(e_lfanew)) != 80 or _tb_cc(head, sizet(e_lfanew) + 1) != 69: return lay
|
||
coff: sizet = sizet(e_lfanew) + 4
|
||
nsec: uint64 = uint64(_tb_rd_u16(head, coff + 2))
|
||
optsz: uint64 = uint64(_tb_rd_u16(head, coff + 16))
|
||
opt: sizet = coff + 20
|
||
if uint64(opt) + 32 > uint64(got): return lay
|
||
magic: uint64 = uint64(_tb_rd_u16(head, opt))
|
||
img_base: uint64 = 0
|
||
if magic == 0x20B: img_base = _tb_rd_u64(head, opt + 24)
|
||
elif magic == 0x10B: img_base = uint64(_tb_rd_u32(head, opt + 28))
|
||
else: return lay
|
||
size_img: uint64 = uint64(_tb_rd_u32(head, opt + 56))
|
||
sec_off: sizet = opt + sizet(optsz)
|
||
text_rva: uint64 = 0x1000
|
||
min_rva: uint64 = 0xFFFFFFFF
|
||
found_text: int = 0
|
||
have_sec: int = 0
|
||
si: uint64 = 0
|
||
while si < nsec:
|
||
base: sizet = sec_off + sizet(si) * 40
|
||
if uint64(base) + 40 > uint64(got): break
|
||
va: uint64 = uint64(_tb_rd_u32(head, base + 12))
|
||
have_sec = 1
|
||
if va < min_rva: min_rva = va
|
||
if _tb_cc(head, base) == 46 and _tb_cc(head, base + 1) == 116 and _tb_cc(head, base + 2) == 101 and _tb_cc(head, base + 3) == 120 and _tb_cc(head, base + 4) == 116:
|
||
text_rva = va
|
||
found_text = 1
|
||
si += 1
|
||
if found_text == 0 and have_sec != 0 and min_rva != 0xFFFFFFFF: text_rva = min_rva
|
||
lay.ImageBase = img_base
|
||
lay.SizeOfImage = size_img
|
||
lay.TextRva = text_rva
|
||
lay.Ok = 1
|
||
return lay
|
||
|
||
|
||
def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump*:
|
||
"""Parse llvm-dwarfdump text into a row table (addr, path, line) and a func table."""
|
||
d: TbDump* = TbDump()
|
||
if d is None: return None
|
||
LINE_CAP: sizet = 8192
|
||
d.RowBuf = mb.alloc(sizet(TB_ROW_CAP0) * TB_ROW_SIZE)
|
||
d.RowCap = TB_ROW_CAP0
|
||
d.FuncBuf = mb.alloc(sizet(TB_FUNC_CAP0) * TB_FUNC_SIZE)
|
||
d.FuncCap = TB_FUNC_CAP0
|
||
dirs: bytes = mb.alloc(sizet(TB_DIR_CAP) * TB_DIR_SIZE)
|
||
files: bytes = mb.alloc(sizet(TB_FILE_CAP) * TB_FILE_SIZE)
|
||
linebuf: bytes = mb.alloc(LINE_CAP)
|
||
if d.RowBuf is None or d.FuncBuf is None or dirs is None or files is None or linebuf is None:
|
||
VLogger.error("--traceback: dwarfdump 解析缓冲分配失败,终止编译", "TRACEBACK")
|
||
return None
|
||
|
||
in_line: int = 0
|
||
dir_count: int = 0
|
||
file_count: int = 0
|
||
pkind: int = 0 # 0=none, 1=file, 2=dir
|
||
pidx: int = 0
|
||
pname: str = None
|
||
pdir: int = 0
|
||
in_prog: int = 0
|
||
cur_low: uint64 = 0
|
||
cur_high: uint64 = 0
|
||
cur_has_low: int = 0
|
||
cur_has_high: int = 0
|
||
cname: str = None
|
||
|
||
dlen: sizet = string.strlen(dump)
|
||
pos: sizet = 0
|
||
while pos < dlen:
|
||
ls: sizet = pos
|
||
while pos < dlen and _tb_cc(dump, pos) != 10: pos += 1
|
||
le: sizet = pos
|
||
if pos < dlen: pos += 1
|
||
if le > ls and _tb_cc(dump, le - 1) == 13: le -= 1
|
||
ll: sizet = le - ls
|
||
if ll == 0: continue
|
||
if ll >= LINE_CAP: ll = LINE_CAP - 1
|
||
string.strncpy(linebuf, dump + ls, ll)
|
||
linebuf[ll] = '\0'
|
||
st: str = _tb_ltrim(linebuf)
|
||
|
||
# ---- line-table section gate ----
|
||
if _tb_contains(st, "debug_line contents") != 0:
|
||
in_line = 1
|
||
continue
|
||
if _tb_contains(st, "contents:") != 0 and _tb_contains(st, "debug_") != 0:
|
||
in_line = 0
|
||
continue
|
||
|
||
if in_line != 0:
|
||
if _tb_startswith(st, "debug_line[") != 0:
|
||
# new CU: commit pending file, reset both tables
|
||
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
|
||
file_count += 1
|
||
pkind = 0
|
||
pname = None
|
||
pdir = 0
|
||
dir_count = 0
|
||
file_count = 0
|
||
continue
|
||
if _tb_startswith(st, "file_names[") != 0:
|
||
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
|
||
file_count += 1
|
||
pname = None
|
||
pdir = 0
|
||
pkind = 1
|
||
pidx = _tb_bracket_idx(st)
|
||
continue
|
||
if _tb_startswith(st, "directory_names[") != 0 or _tb_startswith(st, "include_directories[") != 0:
|
||
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
|
||
file_count += 1
|
||
pname = None
|
||
pdir = 0
|
||
pidx = _tb_bracket_idx(st)
|
||
val: str = _tb_value_after_sep(_tb_after_bracket(st))
|
||
if val is not None and _tb_len_trimmed(val) > 0:
|
||
if dir_count < TB_DIR_CAP:
|
||
_tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx))
|
||
_tb_wr_u64(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, uint64(_tb_unquote(mb, val)))
|
||
dir_count += 1
|
||
pkind = 0
|
||
else: pkind = 2
|
||
continue
|
||
if _tb_startswith(st, "name:") != 0 and pkind != 0:
|
||
val2: str = _tb_unquote(mb, _tb_ltrim(st + 5))
|
||
if pkind == 1: pname = val2
|
||
else:
|
||
if dir_count < TB_DIR_CAP and val2 is not None:
|
||
_tb_wr_u32(dirs, sizet(dir_count) * TB_DIR_SIZE, uint64(pidx))
|
||
_tb_wr_u64(dirs, sizet(dir_count) * TB_DIR_SIZE + 8, uint64(val2))
|
||
dir_count += 1
|
||
pkind = 0
|
||
continue
|
||
if _tb_startswith(st, "dir_index:") != 0 and pkind == 1:
|
||
# `"dir_index:"` 含冒号共 10 字符:st + 9 指向冒号本身,解析恒得 0
|
||
# (目录索引全丢 → 相对文件名拼不出绝对路径,File 行只剩裸文件名)。
|
||
pdir = _tb_parse_dec(_tb_ltrim(st + 10))
|
||
continue
|
||
if _tb_startswith(st, "Address") != 0 and _tb_contains(st, "Column") != 0:
|
||
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
|
||
file_count += 1
|
||
pkind = 0
|
||
pname = None
|
||
pdir = 0
|
||
continue
|
||
if _tb_cc(linebuf, 0) == 48 and _tb_cc(linebuf, 1) == 120:
|
||
if pkind == 1 and pname is not None and file_count < TB_FILE_CAP:
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE, uint64(pidx))
|
||
_tb_wr_u32(files, sizet(file_count) * TB_FILE_SIZE + 4, uint64(pdir))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 8, uint64(pname))
|
||
_tb_wr_u64(files, sizet(file_count) * TB_FILE_SIZE + 24, uint64(0))
|
||
file_count += 1
|
||
pkind = 0
|
||
pname = None
|
||
pdir = 0
|
||
ri: sizet = 2
|
||
addr: uint64 = 0
|
||
hd: int = _tb_hex_digit(_tb_cc(linebuf, ri))
|
||
while hd >= 0:
|
||
addr = (addr << 4) | uint64(hd)
|
||
ri += 1
|
||
hd = _tb_hex_digit(_tb_cc(linebuf, ri))
|
||
while ri < ll and (_tb_cc(linebuf, ri) == 32 or _tb_cc(linebuf, ri) == 9): ri += 1
|
||
lno: int = 0
|
||
while ri < ll and _tb_cc(linebuf, ri) >= 48 and _tb_cc(linebuf, ri) <= 57:
|
||
lno = lno * 10 + (_tb_cc(linebuf, ri) - 48)
|
||
ri += 1
|
||
while ri < ll and (_tb_cc(linebuf, ri) == 32 or _tb_cc(linebuf, ri) == 9): ri += 1
|
||
while ri < ll and _tb_cc(linebuf, ri) >= 48 and _tb_cc(linebuf, ri) <= 57: ri += 1
|
||
while ri < ll and (_tb_cc(linebuf, ri) == 32 or _tb_cc(linebuf, ri) == 9): ri += 1
|
||
fidx: int = 0
|
||
while ri < ll and _tb_cc(linebuf, ri) >= 48 and _tb_cc(linebuf, ri) <= 57:
|
||
fidx = fidx * 10 + (_tb_cc(linebuf, ri) - 48)
|
||
ri += 1
|
||
if lno > 0:
|
||
frec: int = _tb_file_rec(files, file_count, fidx)
|
||
fpath: str = None
|
||
if frec >= 0: fpath = _tb_resolve_file(mb, files, file_count, dirs, dir_count, frec)
|
||
if fpath is None: fpath = "??"
|
||
if _tb_emit_row(mb, d, addr, fpath, lno) != 0: return None
|
||
continue
|
||
|
||
# ---- DWARF debug_info subprogram parsing ----
|
||
if _tb_contains(linebuf, "DW_TAG_") != 0:
|
||
if in_prog != 0 and cur_has_low != 0 and cname is not None and string.strlen(cname) > 0:
|
||
hi: uint64 = cur_low + 1
|
||
if cur_has_high != 0:
|
||
hi = cur_high
|
||
if hi <= cur_low: hi = cur_low + cur_high
|
||
if hi > cur_low:
|
||
if _tb_emit_func(mb, d, cur_low, hi, cname) != 0: return None
|
||
in_prog = 0
|
||
cur_low = 0
|
||
cur_high = 0
|
||
cur_has_low = 0
|
||
cur_has_high = 0
|
||
cname = None
|
||
if _tb_contains(linebuf, "DW_TAG_subprogram") != 0: in_prog = 1
|
||
continue
|
||
if in_prog == 0: continue
|
||
if _tb_contains(linebuf, "DW_AT_low_pc") != 0:
|
||
cur_low = _tb_hex_in_parens(linebuf, "DW_AT_low_pc")
|
||
cur_has_low = 1
|
||
continue
|
||
if _tb_contains(linebuf, "DW_AT_high_pc") != 0:
|
||
cur_high = _tb_hex_in_parens(linebuf, "DW_AT_high_pc")
|
||
cur_has_high = 1
|
||
continue
|
||
if _tb_contains(linebuf, "DW_AT_name") != 0 and cname is None:
|
||
cname = _tb_quoted_after(mb, linebuf, "DW_AT_name")
|
||
continue
|
||
|
||
# trailing subprogram
|
||
if in_prog != 0 and cur_has_low != 0 and cname is not None and string.strlen(cname) > 0:
|
||
hi2: uint64 = cur_low + 1
|
||
if cur_has_high != 0:
|
||
hi2 = cur_high
|
||
if hi2 <= cur_low: hi2 = cur_low + cur_high
|
||
if hi2 > cur_low:
|
||
if _tb_emit_func(mb, d, cur_low, hi2, cname) != 0: return None
|
||
return d
|
||
|
||
|
||
# ============================================================
|
||
# --traceback 调用点归一:.vpcall + llvm-objdump
|
||
#
|
||
# MLIR 后端不产 DWARF(我方 .vp 函数在 exe 里没有 .debug_line),故用编译器在翻译期
|
||
# 落盘的 <sha1>.vpcall 文本 + 链接后对 exe 的 `llvm-objdump -d` 反汇编,把每个直接
|
||
# callq 的**返回地址**(调用方帧的 PC)还原为「文件 + 调用点行 + 调用方函数名」。
|
||
#
|
||
# 匹配口径:objdump 的函数标签地址 → 下一标签地址 构成函数区间;对每条指令行
|
||
# `^([0-9a-f]+): (.. )+\s+(\S+)\s*(.*)$`,`callq 0xADDR <callee>` 的返回地址 = 下一条
|
||
# 指令(或下一个标签)的地址。用「所在函数 + callee 符号 + 该 (caller,callee) 对内的
|
||
# 出现序号」匹配编译器 C 记录(同一 (caller,callee) 在构建序与地址序一致,llc -O0)。
|
||
# 另为我方每个函数在入口地址生成一条兜底记录(F 记录 → (文件, 定义行, 函数名))。
|
||
# ============================================================
|
||
|
||
# 调用记录:u64 caller | u64 callee | u64 file | u32 line | u32 next(组内下一个, NONE=尾)
|
||
define TB_CR_SIZE = 32
|
||
define TB_CR_OFF_CALLER = 0
|
||
define TB_CR_OFF_CALLEE = 8
|
||
define TB_CR_OFF_FILE = 16
|
||
define TB_CR_OFF_LINE = 24
|
||
define TB_CR_OFF_NEXT = 28
|
||
# 函数记录:u64 sym | u64 file | u32 defline | u32 pad
|
||
define TB_FR_SIZE = 24
|
||
define TB_FR_OFF_SYM = 0
|
||
define TB_FR_OFF_FILE = 8
|
||
define TB_FR_OFF_DEFLINE = 16
|
||
# 组哈希槽(以 (caller,callee) 为键;开放寻址 + 线性探测):
|
||
# u64 caller | u64 callee | u32 used | u32 head | u32 tail | u32 cursor
|
||
define TB_GRP_SIZE = 32
|
||
define TB_GRP_OFF_CALLER = 0
|
||
define TB_GRP_OFF_CALLEE = 8
|
||
define TB_GRP_OFF_USED = 16
|
||
define TB_GRP_OFF_HEAD = 20
|
||
define TB_GRP_OFF_TAIL = 24
|
||
define TB_GRP_OFF_CURSOR = 28
|
||
# 无效索引哨兵(u32 全 1)
|
||
TB_IDX_NONE: uint64 = 0xFFFFFFFF
|
||
|
||
|
||
class TbCallParse:
|
||
Cr: bytes # TB_CR_SIZE 记录数组
|
||
CrCap: int
|
||
CrN: int
|
||
Fr: bytes # TB_FR_SIZE 记录数组
|
||
FrCap: int
|
||
FrN: int
|
||
Grp: bytes # TB_GRP_SIZE 槽数组
|
||
GrpCap: int
|
||
|
||
def __new__(self) -> t.CPtr:
|
||
return t.CPtr(_mbuddy.alloc(TbCallParse.__sizeof__()))
|
||
|
||
def __init__(self):
|
||
self.Cr = None
|
||
self.CrCap = 0
|
||
self.CrN = 0
|
||
self.Fr = None
|
||
self.FrCap = 0
|
||
self.FrN = 0
|
||
self.Grp = None
|
||
self.GrpCap = 0
|
||
|
||
|
||
def _tb_next_pow2(n: int) -> int:
|
||
p: int = 16
|
||
while p < n: p = p * 2
|
||
return p
|
||
|
||
|
||
def _tb_strhash(s: str) -> uint64:
|
||
"""FNV-1a(32 位起点 + 64 位素数,仅用于哈希表寻址)。"""
|
||
h: uint64 = 0x811C9DC5
|
||
if s is None: return h
|
||
i: sizet = 0
|
||
while _tb_cc(s, i) != 0:
|
||
h = (h ^ uint64(_tb_cc(s, i))) * 0x100000001B3
|
||
i += 1
|
||
return h
|
||
|
||
|
||
def _tb_grp_get(grp: bytes, cap: int, caller: str, callee: str, do_insert: int) -> int:
|
||
"""按 (caller,callee) 查槽;do_insert=1 时未命中就地插入。返回槽号或 -1。"""
|
||
if grp is None or cap <= 0 or caller is None or callee is None: return -1
|
||
mask: uint64 = uint64(cap - 1)
|
||
hv: uint64 = (_tb_strhash(caller) * 0x100000001B3 + _tb_strhash(callee)) & mask
|
||
i: int = 0
|
||
while i < cap:
|
||
slot: int = int((hv + uint64(i)) & mask)
|
||
off: sizet = sizet(slot) * TB_GRP_SIZE
|
||
if uint64(_tb_rd_u32(grp, off + TB_GRP_OFF_USED)) == 0:
|
||
if do_insert == 0: return -1
|
||
_tb_wr_u32(grp, off + TB_GRP_OFF_USED, uint64(1))
|
||
_tb_wr_u64(grp, off + TB_GRP_OFF_CALLER, uint64(caller))
|
||
_tb_wr_u64(grp, off + TB_GRP_OFF_CALLEE, uint64(callee))
|
||
_tb_wr_u32(grp, off + TB_GRP_OFF_HEAD, uint64(TB_IDX_NONE))
|
||
_tb_wr_u32(grp, off + TB_GRP_OFF_TAIL, uint64(TB_IDX_NONE))
|
||
_tb_wr_u32(grp, off + TB_GRP_OFF_CURSOR, uint64(TB_IDX_NONE))
|
||
return slot
|
||
sc: str = rawptr(_tb_rd_u64(grp, off + TB_GRP_OFF_CALLER))
|
||
sk: str = rawptr(_tb_rd_u64(grp, off + TB_GRP_OFF_CALLEE))
|
||
if sc is not None and sk is not None and string.strcmp(sc, caller) == 0 and string.strcmp(sk, callee) == 0: return slot
|
||
i += 1
|
||
return -1
|
||
|
||
|
||
def _tb_cp_init(mb: memhub.MemBuddy*) -> TbCallParse*:
|
||
cp: TbCallParse* = TbCallParse()
|
||
if cp is None: return None
|
||
cp.CrCap = 1024
|
||
cp.FrCap = 1024
|
||
cp.Cr = mb.alloc(sizet(cp.CrCap) * TB_CR_SIZE)
|
||
cp.Fr = mb.alloc(sizet(cp.FrCap) * TB_FR_SIZE)
|
||
if cp.Cr is None or cp.Fr is None: return None
|
||
cp.CrN = 0
|
||
cp.FrN = 0
|
||
return cp
|
||
|
||
|
||
def _tb_cp_add_cr(mb: memhub.MemBuddy*, cp: TbCallParse*, caller: str, callee: str, file: str, line: int) -> int:
|
||
if cp.CrN >= cp.CrCap:
|
||
ncap: int = cp.CrCap * 2
|
||
nb: bytes = _tb_grow_rec(mb, cp.Cr, cp.CrCap, TB_CR_SIZE, ncap)
|
||
if nb is None:
|
||
VLogger.error("--traceback: 调用记录表扩容失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
cp.Cr = nb
|
||
cp.CrCap = ncap
|
||
off: sizet = sizet(cp.CrN) * TB_CR_SIZE
|
||
_tb_wr_u64(cp.Cr, off + TB_CR_OFF_CALLER, uint64(caller))
|
||
_tb_wr_u64(cp.Cr, off + TB_CR_OFF_CALLEE, uint64(callee))
|
||
_tb_wr_u64(cp.Cr, off + TB_CR_OFF_FILE, uint64(file))
|
||
_tb_wr_u32(cp.Cr, off + TB_CR_OFF_LINE, uint64(line))
|
||
_tb_wr_u32(cp.Cr, off + TB_CR_OFF_NEXT, uint64(TB_IDX_NONE))
|
||
cp.CrN = cp.CrN + 1
|
||
return 0
|
||
|
||
|
||
def _tb_cp_add_fr(mb: memhub.MemBuddy*, cp: TbCallParse*, sym: str, file: str, defline: int) -> int:
|
||
if cp.FrN >= cp.FrCap:
|
||
ncap: int = cp.FrCap * 2
|
||
nb: bytes = _tb_grow_rec(mb, cp.Fr, cp.FrCap, TB_FR_SIZE, ncap)
|
||
if nb is None:
|
||
VLogger.error("--traceback: 函数记录表扩容失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
cp.Fr = nb
|
||
cp.FrCap = ncap
|
||
off: sizet = sizet(cp.FrN) * TB_FR_SIZE
|
||
_tb_wr_u64(cp.Fr, off + TB_FR_OFF_SYM, uint64(sym))
|
||
_tb_wr_u64(cp.Fr, off + TB_FR_OFF_FILE, uint64(file))
|
||
_tb_wr_u32(cp.Fr, off + TB_FR_OFF_DEFLINE, uint64(defline))
|
||
cp.FrN = cp.FrN + 1
|
||
return 0
|
||
|
||
|
||
def _tb_read_whole(mb: memhub.MemBuddy*, path: str) -> str:
|
||
"""读整个文件为 NUL 结尾字符串(失败返回 None)。"""
|
||
f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R)
|
||
if f.closed: return None
|
||
sz: LONGLONG = f.size()
|
||
if sz < 0:
|
||
f.close()
|
||
return None
|
||
buf: str = mb.alloc(sizet(sz) + 2)
|
||
if buf is None:
|
||
f.close()
|
||
return None
|
||
got: LONG = f.read_all(buf, ULONG(sz) + ULONG(2))
|
||
f.close()
|
||
if got < 0: return None
|
||
return buf
|
||
|
||
|
||
def _tb_parse_f_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCallParse*) -> int:
|
||
"""解析 `F\t<sym>\t<defline>\t<file>`。"""
|
||
p: sizet = a
|
||
while p < b and _tb_cc(s, p) != 9: p += 1
|
||
if p >= b: return 0
|
||
p += 1
|
||
f2: sizet = p
|
||
while f2 < b and _tb_cc(s, f2) != 9: f2 += 1
|
||
if f2 >= b: return 0
|
||
sym: str = _tb_dup_span(mb, s + p, f2 - p)
|
||
p = f2 + 1
|
||
f3: sizet = p
|
||
while f3 < b and _tb_cc(s, f3) != 9: f3 += 1
|
||
if f3 >= b: return 0
|
||
defline: int = _tb_parse_dec(s + p)
|
||
p = f3 + 1
|
||
file: str = _tb_dup_span(mb, s + p, b - p)
|
||
if sym is None or file is None: return 0
|
||
return _tb_cp_add_fr(mb, cp, sym, file, defline)
|
||
|
||
|
||
def _tb_parse_c_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, cp: TbCallParse*) -> int:
|
||
"""解析 `C\t<caller>\t<callee>\t<line>\t<file>`。"""
|
||
p: sizet = a
|
||
while p < b and _tb_cc(s, p) != 9: p += 1
|
||
if p >= b: return 0
|
||
p += 1
|
||
e2: sizet = p
|
||
while e2 < b and _tb_cc(s, e2) != 9: e2 += 1
|
||
if e2 >= b: return 0
|
||
caller: str = _tb_dup_span(mb, s + p, e2 - p)
|
||
p = e2 + 1
|
||
e3: sizet = p
|
||
while e3 < b and _tb_cc(s, e3) != 9: e3 += 1
|
||
if e3 >= b: return 0
|
||
callee: str = _tb_dup_span(mb, s + p, e3 - p)
|
||
p = e3 + 1
|
||
e4: sizet = p
|
||
while e4 < b and _tb_cc(s, e4) != 9: e4 += 1
|
||
if e4 >= b: return 0
|
||
line: int = _tb_parse_dec(s + p)
|
||
p = e4 + 1
|
||
file: str = _tb_dup_span(mb, s + p, b - p)
|
||
if caller is None or callee is None or file is None: return 0
|
||
return _tb_cp_add_cr(mb, cp, caller, callee, file, line)
|
||
|
||
|
||
def _tb_parse_vpcall_file(mb: memhub.MemBuddy*, path: str, cp: TbCallParse*) -> int:
|
||
whole: str = _tb_read_whole(mb, path)
|
||
if whole is None:
|
||
VLogger.error(f"--traceback: 读取 .vpcall 失败:{path},终止编译", "TRACEBACK")
|
||
return 1
|
||
n: sizet = string.strlen(whole)
|
||
pos: sizet = 0
|
||
while pos < n:
|
||
ls: sizet = pos
|
||
while pos < n and _tb_cc(whole, pos) != 10: pos += 1
|
||
le: sizet = pos
|
||
if pos < n: pos += 1
|
||
if le > ls and _tb_cc(whole, le - 1) == 13: le -= 1
|
||
if le > ls:
|
||
typ: int = _tb_cc(whole, ls)
|
||
if typ == 70:
|
||
if _tb_parse_f_line(mb, whole, ls, le, cp) != 0: return 1
|
||
elif typ == 67:
|
||
if _tb_parse_c_line(mb, whole, ls, le, cp) != 0: return 1
|
||
return 0
|
||
|
||
|
||
def _tb_load_vpcalls(mb: memhub.MemBuddy*, cp: TbCallParse*) -> int:
|
||
if _tb_vpcall_buf is None or _tb_vpcall_n <= 0: return 0
|
||
k: int = 0
|
||
while k < _tb_vpcall_n:
|
||
slot: str* = (str*)(uint64(_tb_vpcall_buf) + sizet(k) * 8)
|
||
path: str = slot[0]
|
||
if path is not None:
|
||
if _tb_parse_vpcall_file(mb, path, cp) != 0: return 1
|
||
k += 1
|
||
return 0
|
||
|
||
|
||
def _tb_build_groups(mb: memhub.MemBuddy*, cp: TbCallParse*) -> int:
|
||
"""把所有 C 记录按 (caller,callee) 串成组链表(组槽存 head/tail/cursor)。"""
|
||
if cp.CrN <= 0: return 0
|
||
cp.GrpCap = _tb_next_pow2(cp.CrN * 2 + 16)
|
||
cp.Grp = mb.alloc(sizet(cp.GrpCap) * TB_GRP_SIZE)
|
||
if cp.Grp is None:
|
||
VLogger.error("--traceback: 调用点分组表分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
string.memset(cp.Grp, 0, sizet(cp.GrpCap) * TB_GRP_SIZE)
|
||
i: int = 0
|
||
while i < cp.CrN:
|
||
off: sizet = sizet(i) * TB_CR_SIZE
|
||
caller: str = rawptr(_tb_rd_u64(cp.Cr, off + TB_CR_OFF_CALLER))
|
||
callee: str = rawptr(_tb_rd_u64(cp.Cr, off + TB_CR_OFF_CALLEE))
|
||
slot: int = _tb_grp_get(cp.Grp, cp.GrpCap, caller, callee, 1)
|
||
if slot < 0:
|
||
VLogger.error("--traceback: 调用点分组哈希表溢出,终止编译", "TRACEBACK")
|
||
return 1
|
||
goff: sizet = sizet(slot) * TB_GRP_SIZE
|
||
tailv: uint64 = uint64(_tb_rd_u32(cp.Grp, goff + TB_GRP_OFF_TAIL))
|
||
if tailv == uint64(TB_IDX_NONE):
|
||
_tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_HEAD, uint64(i))
|
||
_tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_CURSOR, uint64(i))
|
||
else:
|
||
toff: sizet = sizet(int(tailv)) * TB_CR_SIZE
|
||
_tb_wr_u32(cp.Cr, toff + TB_CR_OFF_NEXT, uint64(i))
|
||
_tb_wr_u32(cp.Cr, off + TB_CR_OFF_NEXT, uint64(TB_IDX_NONE))
|
||
_tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_TAIL, uint64(i))
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _tb_emit_call_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo: uint64, caller: str, callee: str, ret_va: uint64) -> int:
|
||
"""按 (caller,callee) 的下一条未消费 C 记录为返回地址生成一条行记录。"""
|
||
if caller is None or callee is None or cp.Grp is None or ret_va < img_lo: return 0
|
||
slot: int = _tb_grp_get(cp.Grp, cp.GrpCap, caller, callee, 0)
|
||
if slot < 0: return 0
|
||
goff: sizet = sizet(slot) * TB_GRP_SIZE
|
||
curv: uint64 = uint64(_tb_rd_u32(cp.Grp, goff + TB_GRP_OFF_CURSOR))
|
||
if curv == uint64(TB_IDX_NONE): return 0
|
||
ci: int = int(curv)
|
||
coff: sizet = sizet(ci) * TB_CR_SIZE
|
||
file: str = rawptr(_tb_rd_u64(cp.Cr, coff + TB_CR_OFF_FILE))
|
||
line: int = int(_tb_rd_u32(cp.Cr, coff + TB_CR_OFF_LINE))
|
||
nxt: uint64 = uint64(_tb_rd_u32(cp.Cr, coff + TB_CR_OFF_NEXT))
|
||
_tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_CURSOR, nxt)
|
||
return _tb_emit_row(mb, d, ret_va, file, line, caller)
|
||
|
||
|
||
def _tb_emit_entry_row(mb: memhub.MemBuddy*, d: TbDump*, cp: TbCallParse*, img_lo: uint64, addr: uint64, sym: str) -> int:
|
||
"""为 objdump 标签 `sym` 生成函数入口兜底行(文件/定义行取自 F 记录)。"""
|
||
if sym is None or cp.Fr is None or cp.FrN <= 0 or addr < img_lo: return 0
|
||
i: int = 0
|
||
while i < cp.FrN:
|
||
off: sizet = sizet(i) * TB_FR_SIZE
|
||
s: str = rawptr(_tb_rd_u64(cp.Fr, off + TB_FR_OFF_SYM))
|
||
if s is not None and string.strcmp(s, sym) == 0:
|
||
file: str = rawptr(_tb_rd_u64(cp.Fr, off + TB_FR_OFF_FILE))
|
||
dl: int = int(_tb_rd_u32(cp.Fr, off + TB_FR_OFF_DEFLINE))
|
||
return _tb_emit_row(mb, d, addr, file, dl, sym)
|
||
i += 1
|
||
return 0
|
||
|
||
|
||
def _tb_hex_span(s: str, a: sizet, b: sizet) -> uint64:
|
||
v: uint64 = 0
|
||
i: sizet = a
|
||
while i < b:
|
||
hd: int = _tb_hex_digit(_tb_cc(s, i))
|
||
if hd < 0: break
|
||
v = (v << 4) | uint64(hd)
|
||
i += 1
|
||
return v
|
||
|
||
|
||
def _tb_run_objdump(mb: memhub.MemBuddy*, exe_path: str) -> str:
|
||
cl: sizet = string.strlen(exe_path) + 64
|
||
cmd: bytes = mb.alloc(cl)
|
||
if cmd is None:
|
||
VLogger.error("--traceback: objdump 命令缓冲分配失败,终止编译", "TRACEBACK")
|
||
return None
|
||
viperlib.snprintf(cmd, cl, "llvm-objdump -d \"%s\"", exe_path)
|
||
res: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True)
|
||
if res is None or res.returncode != 0:
|
||
rc: int = -1
|
||
if res is not None: rc = res.returncode
|
||
VLogger.error(f"--traceback: 调用 llvm-objdump 失败(返回码 {rc};请确认 PATH 内含 LLVM 工具),终止编译", "TRACEBACK")
|
||
return None
|
||
dump: str = res.stdout
|
||
if dump is None:
|
||
VLogger.error("--traceback: llvm-objdump 无输出,终止编译", "TRACEBACK")
|
||
return None
|
||
return dump
|
||
|
||
|
||
def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, d: TbDump*, cp: TbCallParse*) -> int:
|
||
"""解析 `llvm-objdump -d` 文本,匹配 C/F 记录并把我方行写入 d。"""
|
||
dlen: sizet = string.strlen(dump)
|
||
pos: sizet = 0
|
||
in_text: int = 0
|
||
cur_func: str = None
|
||
prev_is_call: int = 0
|
||
prev_callee: str = None
|
||
prev_func: str = None
|
||
while pos < dlen:
|
||
ls: sizet = pos
|
||
while pos < dlen and _tb_cc(dump, pos) != 10: pos += 1
|
||
le: sizet = pos
|
||
if pos < dlen: pos += 1
|
||
if le > ls and _tb_cc(dump, le - 1) == 13: le -= 1
|
||
if le <= ls: continue
|
||
if _tb_startswith(dump + ls, "Disassembly of section") != 0:
|
||
# 跨段:上一个段末尾未决的 callq 无有效返回地址,丢弃
|
||
prev_is_call = 0
|
||
if _tb_contains(dump + ls, ".text") != 0: in_text = 1
|
||
else: in_text = 0
|
||
continue
|
||
if in_text == 0: continue
|
||
c0: int = _tb_cc(dump, ls)
|
||
is_hex0: int = 0
|
||
if (c0 >= 48 and c0 <= 57) or (c0 >= 97 and c0 <= 102) or (c0 >= 65 and c0 <= 70): is_hex0 = 1
|
||
if is_hex0 == 0: continue
|
||
# 首个空格
|
||
sp: sizet = ls
|
||
while sp < le and _tb_cc(dump, sp) != 32: sp += 1
|
||
if sp + 1 < le and _tb_cc(dump, sp + 1) == 60:
|
||
# ---- 标签行 `ADDR <name>:` ----
|
||
laddr: uint64 = _tb_hex_span(dump, ls, sp)
|
||
if prev_is_call != 0:
|
||
_tb_emit_call_row(mb, d, cp, img_lo, prev_func, prev_callee, laddr)
|
||
prev_is_call = 0
|
||
lb: sizet = sp + 2
|
||
j: sizet = lb
|
||
while j < le and _tb_cc(dump, j) != 62: j += 1
|
||
cur_func = _tb_dup_span(mb, dump + lb, j - lb)
|
||
if cur_func is not None: _tb_emit_entry_row(mb, d, cp, img_lo, laddr, cur_func)
|
||
continue
|
||
# ---- 指令行 `ADDR: bytes mnemonic operands` ----
|
||
k: sizet = ls
|
||
while k < le and _tb_cc(dump, k) != 58: k += 1
|
||
if k >= le: continue
|
||
iaddr: uint64 = _tb_hex_span(dump, ls, k)
|
||
if prev_is_call != 0:
|
||
_tb_emit_call_row(mb, d, cp, img_lo, prev_func, prev_callee, iaddr)
|
||
prev_is_call = 0
|
||
# 跳过字节 token(恰好 2 个 hex 字符),定位 mnemonic 起点
|
||
p: sizet = k + 1
|
||
while p < le and (_tb_cc(dump, p) == 32 or _tb_cc(dump, p) == 9): p += 1
|
||
ms: sizet = p
|
||
while p < le:
|
||
ts: sizet = p
|
||
while p < le and _tb_cc(dump, p) != 32 and _tb_cc(dump, p) != 9: p += 1
|
||
tl: sizet = p - ts
|
||
ishex: int = 0
|
||
if tl == 2 and _tb_hex_digit(_tb_cc(dump, ts)) >= 0 and _tb_hex_digit(_tb_cc(dump, ts + 1)) >= 0: ishex = 1
|
||
ms = ts
|
||
if ishex == 0: break
|
||
while p < le and (_tb_cc(dump, p) == 32 or _tb_cc(dump, p) == 9): p += 1
|
||
me: sizet = p
|
||
mlen: sizet = me - ms
|
||
is_call: int = 0
|
||
if mlen == 5 and string.strncmp(dump + ms, "callq", 5) == 0: is_call = 1
|
||
elif mlen == 4 and string.strncmp(dump + ms, "call", 4) == 0: is_call = 1
|
||
if is_call != 0:
|
||
os: sizet = me
|
||
while os < le and (_tb_cc(dump, os) == 32 or _tb_cc(dump, os) == 9): os += 1
|
||
if os + 1 < le and _tb_cc(dump, os) == 48 and _tb_cc(dump, os + 1) == 120:
|
||
# 直接调用:提取 `<callee>`
|
||
ab: sizet = os
|
||
while ab < le and _tb_cc(dump, ab) != 60: ab += 1
|
||
if ab < le:
|
||
cb: sizet = ab + 1
|
||
ce: sizet = cb
|
||
while ce < le and _tb_cc(dump, ce) != 62: ce += 1
|
||
xe: sizet = cb
|
||
while xe < ce and _tb_cc(dump, xe) != 43: xe += 1
|
||
prev_callee = _tb_dup_span(mb, dump + cb, xe - cb)
|
||
prev_func = cur_func
|
||
prev_is_call = 1
|
||
return 0
|
||
|
||
|
||
def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int:
|
||
"""--traceback: export <exe>.stackmap after a successful link. 0 = ok."""
|
||
if mb is None or exe_path is None:
|
||
VLogger.error("--traceback: WriteStackMap 参数为空,终止编译", "TRACEBACK")
|
||
return 1
|
||
|
||
lay: TbPeLayout* = _tb_read_pe_layout(mb, exe_path)
|
||
if lay is None or lay.Ok == 0:
|
||
VLogger.error(f"--traceback: 只支持 Windows PE 产物,但 {exe_path} 不是有效 PE(这是 --traceback 的要求)", "TRACEBACK")
|
||
return 1
|
||
size_of_image: uint64 = lay.SizeOfImage
|
||
if size_of_image == 0:
|
||
VLogger.error(f"--traceback: 读到的 PE.SizeOfImage 非正({size_of_image}),无法界定 RVA 域(这是 --traceback 的要求)", "TRACEBACK")
|
||
return 1
|
||
img_lo: uint64 = lay.ImageBase
|
||
img_hi: uint64 = lay.ImageBase + size_of_image
|
||
text_rva: uint64 = lay.TextRva
|
||
|
||
# ---- 1) run llvm-dwarfdump (bare name: CreateProcessA resolves it via PATH) ----
|
||
cmd_len: sizet = string.strlen(exe_path) + 64
|
||
cmd: bytes = mb.alloc(cmd_len)
|
||
if cmd is None:
|
||
VLogger.error("--traceback: dwarfdump 命令缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
viperlib.snprintf(cmd, cmd_len, "llvm-dwarfdump --debug-line --debug-info \"%s\"", exe_path)
|
||
res: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True)
|
||
if res is None or res.returncode != 0:
|
||
rc: int = -1
|
||
if res is not None: rc = res.returncode
|
||
VLogger.error(f"--traceback: 调用 llvm-dwarfdump 失败(返回码 {rc};请确认 PATH 内含 LLVM 工具),终止编译", "TRACEBACK")
|
||
return 1
|
||
dump: str = res.stdout
|
||
if dump is None:
|
||
VLogger.error("--traceback: llvm-dwarfdump 无输出,终止编译", "TRACEBACK")
|
||
return 1
|
||
|
||
d: TbDump* = _tb_parse_dump(mb, dump)
|
||
if d is None: return 1
|
||
|
||
# ---- 1b) 我方调用点行记录:.vpcall + llvm-objdump ----
|
||
# DWARF 行仅覆盖链接进来的 mingw CRT(我方 .vp 函数无 .debug_line);这里用编译器
|
||
# 落盘的 .vpcall + objdump 反汇编补上「返回地址 → 调用点行 / 函数入口行」。
|
||
if _tb_vpcall_n > 0:
|
||
cp: TbCallParse* = _tb_cp_init(mb)
|
||
if cp is None:
|
||
VLogger.error("--traceback: 调用点解析状态分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
if _tb_load_vpcalls(mb, cp) != 0: return 1
|
||
if _tb_build_groups(mb, cp) != 0: return 1
|
||
odump: str = _tb_run_objdump(mb, exe_path)
|
||
if odump is None: return 1
|
||
our_before: int = d.RowCount
|
||
if _tb_scan_objdump(mb, odump, img_lo, d, cp) != 0: return 1
|
||
our_added: int = d.RowCount - our_before
|
||
if our_added > 0: VLogger.success(f"--traceback: 从 {_tb_vpcall_n} 个 .vpcall 生成 {our_added} 条我方调用点记录(F={cp.FrN} 函数, C={cp.CrN} 调用)", "TRACEBACK")
|
||
else: VLogger.warning(f"--traceback: 读到 {_tb_vpcall_n} 个 .vpcall(F={cp.FrN}, C={cp.CrN})但未匹配到任何 objdump 调用点——我方帧行号仍缺,退化到 DWARF", "TRACEBACK")
|
||
else: VLogger.warning("--traceback: 未登记任何 .vpcall(我方模块无调用点记录),退化到 DWARF 行为", "TRACEBACK")
|
||
|
||
if d.RawRowCount == 0:
|
||
VLogger.error(f"--traceback: 未能从 {exe_path} 解析出任何行记录(DWARF .debug_line 为空且无我方 .vpcall 记录,这是 --traceback 的要求)", "TRACEBACK")
|
||
return 1
|
||
|
||
# ---- 2) rows: normalise to RVA, same rva keeps the largest line ----
|
||
n: int = d.RowCount
|
||
dropped_rows: int = 0
|
||
kept: int = 0
|
||
i: int = 0
|
||
while i < n:
|
||
a: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE)
|
||
if a < img_lo or a >= img_hi:
|
||
dropped_rows += 1
|
||
i += 1
|
||
continue
|
||
if kept != i:
|
||
_tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE, a - img_lo)
|
||
_tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_FILE, _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE))
|
||
_tb_wr_u32(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_LINE, uint64(_tb_rd_u32(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_LINE)))
|
||
_tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_FUNC, _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC))
|
||
else: _tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE, a - img_lo)
|
||
kept += 1
|
||
i += 1
|
||
tmp: bytes = mb.alloc(TB_ROW_SIZE)
|
||
if tmp is None:
|
||
VLogger.error("--traceback: 行记录排序缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
si2: int = 1
|
||
while si2 < kept:
|
||
string.memcpy(tmp, d.RowBuf + sizet(si2) * TB_ROW_SIZE, TB_ROW_SIZE)
|
||
sj: int = si2
|
||
while sj > 0:
|
||
if _tb_rd_u64(d.RowBuf, sizet(sj - 1) * TB_ROW_SIZE) <= _tb_rd_u64(tmp, 0): break
|
||
string.memcpy(d.RowBuf + sizet(sj) * TB_ROW_SIZE, d.RowBuf + sizet(sj - 1) * TB_ROW_SIZE, TB_ROW_SIZE)
|
||
sj -= 1
|
||
string.memcpy(d.RowBuf + sizet(sj) * TB_ROW_SIZE, tmp, TB_ROW_SIZE)
|
||
si2 += 1
|
||
uniq: int = 0
|
||
i = 0
|
||
while i < kept:
|
||
rv: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE)
|
||
bl: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_LINE))
|
||
bp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)
|
||
bf: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC)
|
||
j: int = i + 1
|
||
while j < kept and _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE) == rv:
|
||
lj: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_LINE))
|
||
if lj > bl:
|
||
bl = lj
|
||
bp = _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_FILE)
|
||
bf = _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_FUNC)
|
||
j += 1
|
||
if uniq != i:
|
||
_tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE, rv)
|
||
_tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_FILE, bp)
|
||
_tb_wr_u32(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_LINE, bl)
|
||
_tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_FUNC, bf)
|
||
uniq += 1
|
||
i = j
|
||
if dropped_rows > 0: VLogger.warning(f"--traceback: 丢弃 {dropped_rows} 条无法归一化的 DWARF 行记录(地址不在影像 VA 区间,多为 COMDAT 去重副本)", "TRACEBACK")
|
||
if uniq == 0:
|
||
VLogger.error(f"--traceback: 从 {exe_path} 得到的行记录全部无法归一到运行期 RVA 域,终止编译", "TRACEBACK")
|
||
return 1
|
||
|
||
# ---- 3) subprograms: normalise + sort by low_rva ----
|
||
fcount: int = d.FuncCount
|
||
fkept: int = 0
|
||
dropped_funcs: int = 0
|
||
fi: int = 0
|
||
while fi < fcount:
|
||
low: uint64 = _tb_rd_u64(d.FuncBuf, sizet(fi) * TB_FUNC_SIZE)
|
||
if low < img_lo or low >= img_hi:
|
||
dropped_funcs += 1
|
||
fi += 1
|
||
continue
|
||
high: uint64 = _tb_rd_u64(d.FuncBuf, sizet(fi) * TB_FUNC_SIZE + 8)
|
||
lo_rva: uint64 = low - img_lo
|
||
hi_rva: uint64 = high - img_lo
|
||
if hi_rva <= lo_rva: hi_rva = lo_rva + 1
|
||
if hi_rva > size_of_image: hi_rva = size_of_image
|
||
_tb_wr_u64(d.FuncBuf, sizet(fkept) * TB_FUNC_SIZE, lo_rva)
|
||
_tb_wr_u64(d.FuncBuf, sizet(fkept) * TB_FUNC_SIZE + 8, hi_rva)
|
||
_tb_wr_u64(d.FuncBuf, sizet(fkept) * TB_FUNC_SIZE + 16, _tb_rd_u64(d.FuncBuf, sizet(fi) * TB_FUNC_SIZE + 16))
|
||
fkept += 1
|
||
fi += 1
|
||
ftmp: bytes = mb.alloc(TB_FUNC_SIZE)
|
||
if ftmp is None:
|
||
VLogger.error("--traceback: 子程序排序缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
fj: int = 1
|
||
while fj < fkept:
|
||
string.memcpy(ftmp, d.FuncBuf + sizet(fj) * TB_FUNC_SIZE, TB_FUNC_SIZE)
|
||
fk: int = fj
|
||
while fk > 0:
|
||
if _tb_rd_u64(d.FuncBuf, sizet(fk - 1) * TB_FUNC_SIZE) <= _tb_rd_u64(ftmp, 0): break
|
||
string.memcpy(d.FuncBuf + sizet(fk) * TB_FUNC_SIZE, d.FuncBuf + sizet(fk - 1) * TB_FUNC_SIZE, TB_FUNC_SIZE)
|
||
fk -= 1
|
||
string.memcpy(d.FuncBuf + sizet(fk) * TB_FUNC_SIZE, ftmp, TB_FUNC_SIZE)
|
||
fj += 1
|
||
if dropped_funcs > 0: VLogger.warning(f"--traceback: 丢弃 {dropped_funcs} 个无法归一化的 DWARF 子程序(low_pc 不在影像 VA 区间)", "TRACEBACK")
|
||
|
||
# ---- 4) string pool: exact upper bound, then intern (offset 0 = sentinel) ----
|
||
pool_need: sizet = 1
|
||
i = 0
|
||
while i < uniq:
|
||
pth: str = rawptr(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE))
|
||
pool_need += string.strlen(pth) + 1
|
||
i += 1
|
||
# 我方行直接携带函数名(走字符串池);DWARF 行名来自子程序表。
|
||
i = 0
|
||
while i < uniq:
|
||
fp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC)
|
||
if fp != 0:
|
||
fns: str = rawptr(fp)
|
||
if fns is not None: pool_need += string.strlen(fns) + 1
|
||
i += 1
|
||
i = 0
|
||
while i < fkept:
|
||
nm0: str = rawptr(_tb_rd_u64(d.FuncBuf, sizet(i) * TB_FUNC_SIZE + 16))
|
||
if nm0 is not None: pool_need += string.strlen(nm0) + 1
|
||
i += 1
|
||
p: TbPool* = TbPool()
|
||
if p is None:
|
||
VLogger.error("--traceback: 字符串池对象分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
p.Pool = mb.alloc(pool_need)
|
||
p.Offs = mb.alloc((sizet(uniq) * 2 + sizet(fkept)) * 8 + 64)
|
||
if p.Pool is None or p.Offs is None:
|
||
VLogger.error("--traceback: 字符串池缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
p.Pool[0] = '\0'
|
||
p.PoolLen = 1
|
||
_tb_wr_u64(p.Offs, 0, uint64(0))
|
||
p.OffCount = 1
|
||
# file_off / func_off per unique row (u32 each)
|
||
ro: bytes = mb.alloc(sizet(uniq) * 8 + 8)
|
||
if ro is None:
|
||
VLogger.error("--traceback: 记录偏移缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
i = 0
|
||
while i < uniq:
|
||
pth2: str = rawptr(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE))
|
||
foff: uint64 = _tb_intern(p, pth2)
|
||
noff: uint64 = 0
|
||
rv2: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE)
|
||
fp2: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC)
|
||
nm2: str = None
|
||
if fp2 != 0: nm2 = rawptr(fp2)
|
||
else: nm2 = _tb_func_name_for(d.FuncBuf, fkept, rv2)
|
||
if nm2 is not None: noff = _tb_intern(p, nm2)
|
||
_tb_wr_u32(ro, sizet(i) * 8, foff)
|
||
_tb_wr_u32(ro, sizet(i) * 8 + 4, noff)
|
||
i += 1
|
||
|
||
# ---- 5) self-checks (mirror the runtime LoadMapFile validation) ----
|
||
prev_rva: uint64 = 0
|
||
outside: int = 0
|
||
plausible: int = 0
|
||
ci: int = 0
|
||
while ci < uniq:
|
||
cv: uint64 = _tb_rd_u64(d.RowBuf, sizet(ci) * TB_ROW_SIZE)
|
||
if ci > 0 and cv <= prev_rva:
|
||
VLogger.error(f"--traceback 自检失败:记录 RVA 非严格升序(第 {ci} 条会破坏运行期二分查找),终止编译", "TRACEBACK")
|
||
return 1
|
||
prev_rva = cv
|
||
if cv >= size_of_image: outside += 1
|
||
if cv >= text_rva and cv < size_of_image: plausible += 1
|
||
ci += 1
|
||
if outside > 0:
|
||
VLogger.error(f"--traceback 自检失败:有 {outside} 条记录的 RVA 落在 [0, SizeOfImage=0x{size_of_image:x}) 之外,终止编译", "TRACEBACK")
|
||
return 1
|
||
if plausible <= 0:
|
||
VLogger.error("--traceback 自检失败:没有任何记录的 RVA 落在运行期可能出现的区间(RVA 口径疑似错误),终止编译", "TRACEBACK")
|
||
return 1
|
||
# file_off must be >= 1: offset 0 is the empty-string sentinel and the runtime
|
||
# rejects a whole map whose record file_off is 0.
|
||
ci = 0
|
||
while ci < uniq:
|
||
fo: uint64 = uint64(_tb_rd_u32(ro, sizet(ci) * 8))
|
||
if fo == 0:
|
||
VLogger.error("--traceback 自检失败:存在 file_off==0 的记录(空路径,运行期会拒绝该旁文件),终止编译", "TRACEBACK")
|
||
return 1
|
||
ci += 1
|
||
|
||
# ---- 6) serialise and write <exe>.stackmap ----
|
||
total: sizet = 20 + sizet(uniq) * 20 + sizet(p.PoolLen)
|
||
out: bytes = mb.alloc(total)
|
||
if out is None:
|
||
VLogger.error("--traceback: 旁文件输出缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
out[0] = 'T'
|
||
out[1] = 'P'
|
||
out[2] = 'C'
|
||
out[3] = 'S'
|
||
_tb_wr_u32(out, 4, uint64(1))
|
||
_tb_wr_u32(out, 8, uint64(uniq))
|
||
_tb_wr_u32(out, 12, uint64(p.OffCount))
|
||
_tb_wr_u32(out, 16, uint64(p.PoolLen))
|
||
ri2: int = 0
|
||
while ri2 < uniq:
|
||
roff2: sizet = 20 + sizet(ri2) * 20
|
||
rv3: uint64 = _tb_rd_u64(d.RowBuf, sizet(ri2) * TB_ROW_SIZE)
|
||
ln3: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(ri2) * TB_ROW_SIZE + TB_ROW_OFF_LINE))
|
||
_tb_wr_u64(out, roff2, rv3)
|
||
_tb_wr_u32(out, roff2 + 8, uint64(_tb_rd_u32(ro, sizet(ri2) * 8)))
|
||
_tb_wr_u32(out, roff2 + 12, ln3)
|
||
_tb_wr_u32(out, roff2 + 16, uint64(_tb_rd_u32(ro, sizet(ri2) * 8 + 4)))
|
||
ri2 += 1
|
||
string.memcpy(out + 20 + sizet(uniq) * 20, p.Pool, sizet(p.PoolLen))
|
||
|
||
map_len: sizet = string.strlen(exe_path) + 16
|
||
map_path: str = mb.alloc(map_len)
|
||
if map_path is None:
|
||
VLogger.error("--traceback: 旁文件路径缓冲分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
viperlib.snprintf(map_path, map_len, "%s.stackmap", exe_path)
|
||
mf: rawptr[fileio.File] = fileio.File(map_path, fileio.MODE.W)
|
||
if mf.closed:
|
||
VLogger.error(f"--traceback: 写出旁文件失败(无法打开 {map_path}),终止编译", "TRACEBACK")
|
||
return 1
|
||
mf.write(out, total)
|
||
mf.close()
|
||
VLogger.success(f"--traceback: 已写出地址→源行旁文件 {map_path}({uniq} 条记录,{total} 字节)", "TRACEBACK")
|
||
return 0
|
||
|
||
|
||
def run_pipeline(ir_buf: bytes, ir_len: sizet, temp_dir: str, output_dir: str, module_name: str, cc_cmd: str, cc_flags: str, linker_cmd: str, linker_flags: str, linker_output: str, includes_binary_dir: str, linker_static_libs: str = None, linker_dynamic_libs: str = None) -> BuildResult*:
|
||
"""执行完整编译管线
|
||
|
||
Args:
|
||
ir_buf: LLVM IR 文本缓冲区
|
||
ir_len: IR 文本长度
|
||
temp_dir: 临时目录
|
||
output_dir: 输出目录
|
||
module_name: 模块名
|
||
cc_cmd: 编译器命令
|
||
cc_flags: 编译器参数
|
||
linker_cmd: 链接器命令
|
||
linker_flags: 链接器参数
|
||
linker_output: 输出文件名
|
||
includes_binary_dir: includes.binary 目录路径(链接时附加预编译 .obj)
|
||
linker_static_libs: 显式静态链接文件(.a/.obj,空格分隔绝对路径,可为 None)
|
||
|
||
Returns:
|
||
BuildResult 对象
|
||
"""
|
||
result: BuildResult* = BuildResult()
|
||
if result is None: return None
|
||
|
||
# Step 0: 确保 temp/output 目录存在(自动创建,避免写文件失败)
|
||
ensure_dir(temp_dir)
|
||
ensure_dir(output_dir)
|
||
|
||
# Step 1: 写 .ll 文件
|
||
ret: int = write_ir_to_file(ir_buf, ir_len, temp_dir, module_name)
|
||
if ret != 0:
|
||
result.Success = 0
|
||
result.ErrorMsg = "写 .ll 文件失败"
|
||
return result
|
||
|
||
# Step 2: llc 编译 .ll → .obj
|
||
# 构造切片 .ll 文件路径
|
||
ir_path: str = build_sliced_path(temp_dir, module_name, "ll")
|
||
if ir_path is None:
|
||
result.Success = 0
|
||
result.ErrorMsg = "内存分配失败"
|
||
return result
|
||
|
||
ret = compile_ll_to_obj(ir_path, output_dir, module_name, cc_cmd, cc_flags)
|
||
if ret != 0:
|
||
result.Success = 0
|
||
result.ErrorMsg = "llc 编译失败"
|
||
return result
|
||
|
||
# Step 3: clang++ 链接 .obj → .exe(附加 includes.binary 预编译 .obj 与 static_libs/dynamic_libs)
|
||
ret = link_obj_to_exe(output_dir, module_name, linker_cmd, linker_flags, linker_output, includes_binary_dir, linker_static_libs, linker_dynamic_libs)
|
||
if ret != 0:
|
||
result.Success = 0
|
||
result.ErrorMsg = "链接失败"
|
||
return result
|
||
|
||
result.Success = 1
|
||
return result |