Phase A-pre(App 预注册)此前不做导入处理、不注入依赖 .vpi,导致包重导出 类的字段注解在预注册期只能保留「包前缀」(1abcd…),并被冻结进结构体布局; 阶段 A 构造函数侧却能借包别名 entry 校正为真定义前缀(e70bd…),两处票名 分裂 ⇒「非基本类型指针不可隐式转换」(现场 ClassGenGeneric.vp:92)。 改动(只放开 App 预注册,不动 includes 预注册那条防依赖环自锁的约束): - Translator 新增 _pre_allow_dep_inject 开关,并在导入处理门拆成两道: ①导入处理 + 自动容器可见性 + LoadDepVpiIntoModule —— 全量翻译与 App 预注册共用; ②模块级变量预注册 + 函数前向声明 —— 仍只在全量翻译做。 - BuildPipeline.TranslateFileGetTrans 新增 pre_allow_dep_inject 参数并写入 Translator。 - Phase2 App 预注册调用点传 1;includes 预注册保持默认 0。
2293 lines
97 KiB
Plaintext
2293 lines
97 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
|
||
|
||
# 源代码缓冲区大小(1MB)
|
||
SRC_BUF_SIZE: t.CDefine = 1048576 # 2^20 = 1MB
|
||
|
||
# .vpi 存根缓冲区大小(256KB,与 Phase1 一致)
|
||
PYI_BUF_SIZE: sizet = 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
|
||
|
||
|
||
# ============================================================
|
||
# _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 llvmlite, so
|
||
# BuildPipeline gains no cross-module llvmlite 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
|
||
|
||
# --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(ir_path) + 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, ir_path)
|
||
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(ir_path) + 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, ir_path)
|
||
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 核)
|
||
LLC_MAX_THREADS: t.CDefine = 16
|
||
|
||
|
||
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)
|
||
|
||
|
||
def init_llc_task(task: LlcTask | t.CPtr, 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
|
||
# ASan 模式:opt 插桩命令(worker 先跑 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(ir_path) + 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, ir_path)
|
||
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 | t.CPtr):
|
||
"""释放任务的命令串;记录本身是任务数组的连续槽(非独立 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.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(c.Addr(si)), 0, win32process.STARTUPINFOA.__sizeof__())
|
||
string.memset(t.CPtr(c.Addr(sa)), 0, w32.win32base.SECURITY_ATTRIBUTES.__sizeof__())
|
||
string.memset(t.CPtr(c.Addr(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(c.Addr(si)), t.CPtr(c.Addr(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(c.Addr(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 | t.CPtr = (LlcTask | t.CPtr)(arg)
|
||
if task is None:
|
||
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.Result = 1
|
||
return t.CPtr(0)
|
||
llc_rc: int = _run_cmd_wait(task.LlcCmd)
|
||
task.ExitCode = llc_rc
|
||
if llc_rc != 0:
|
||
task.Result = 1
|
||
else:
|
||
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 | t.CPtr = (LlcTask | t.CPtr)(uint64(tasks) + sizet(pi2) * LlcTask.__sizeof__())
|
||
if tsk_p is None:
|
||
continue
|
||
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 | t.CPtr = (LlcTask | t.CPtr)(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 | t.CPtr = (LlcTask | t.CPtr)(uint64(tasks) + sizet(batch_start + j) * slot_size)
|
||
if tsk2 is not None and tsk2.Result != 0:
|
||
# 失败:打印模块 sha1 + 退出码 + 完整命令(.ll 路径即命令末参数)
|
||
VLogger.error(f"llc 编译失败: {(tsk2.ModuleSha1 if tsk2.ModuleSha1 is not None else "?")} (exit={tsk2.ExitCode} cmd={(tsk2.LlcCmd if tsk2.LlcCmd 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 | t.CPtr = _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
|
||
TB_ROW_SIZE: t.CDefine = 24
|
||
TB_ROW_OFF_FILE: t.CDefine = 8
|
||
TB_ROW_OFF_LINE: t.CDefine = 16
|
||
# func record: u64 low_rva | u64 high_rva | u64 name
|
||
TB_FUNC_SIZE: t.CDefine = 24
|
||
# dir record: u32 idx | u32 pad | u64 name
|
||
TB_DIR_SIZE: t.CDefine = 16
|
||
# file record: u32 idx | u32 dir_index | u64 name | u64 resolved_path
|
||
TB_FILE_SIZE: t.CDefine = 32
|
||
# initial capacities for the (growable) row / func tables
|
||
TB_ROW_CAP0: t.CDefine = 65536
|
||
TB_FUNC_CAP0: t.CDefine = 65536
|
||
# fixed per-CU table capacities (exceeding either aborts the build)
|
||
TB_DIR_CAP: t.CDefine = 4096
|
||
TB_FILE_CAP: t.CDefine = 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, t.CUInt64T(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 | t.CPtr,
|
||
addr: uint64, path: str, line: int) -> 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, t.CUInt64T(path))
|
||
_tb_wr_u32(d.RowBuf, off + TB_ROW_OFF_LINE, uint64(line))
|
||
d.RowCount = d.RowCount + 1
|
||
d.RawRowCount = d.RawRowCount + 1
|
||
return 0
|
||
|
||
|
||
def _tb_emit_func(mb: memhub.MemBuddy*, d: TbDump | t.CPtr,
|
||
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, t.CUInt64T(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 | t.CPtr, 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 | t.CPtr:
|
||
"""Read (ImageBase, SizeOfImage, .text RVA) from the PE headers."""
|
||
lay: TbPeLayout | t.CPtr = 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 | t.CPtr:
|
||
"""Parse llvm-dwarfdump text into a row table (addr, path, line) and a func table."""
|
||
d: TbDump | t.CPtr = 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, t.CUInt64T(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, t.CUInt64T(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, t.CUInt64T(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, t.CUInt64T(_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, t.CUInt64T(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, t.CUInt64T(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, t.CUInt64T(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
|
||
|
||
|
||
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 | t.CPtr = _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 | t.CPtr = _tb_parse_dump(mb, dump)
|
||
if d is None:
|
||
return 1
|
||
if d.RawRowCount == 0:
|
||
VLogger.error(f"--traceback: 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?这是 --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)))
|
||
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)
|
||
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)
|
||
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)
|
||
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
|
||
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 | t.CPtr = TbPool()
|
||
if p is None:
|
||
VLogger.error("--traceback: 字符串池对象分配失败,终止编译", "TRACEBACK")
|
||
return 1
|
||
p.Pool = mb.alloc(pool_need)
|
||
p.Offs = mb.alloc(sizet(uniq + 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)
|
||
nm2: str = _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 | t.CPtr:
|
||
"""执行完整编译管线
|
||
|
||
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 | t.CPtr = 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 |