1113 lines
50 KiB
Plaintext
1113 lines
50 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.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: rawptr[memhub.MemBuddy]
|
||
|
||
# ASan 模式(--asan):对 .ll 先 opt -passes=asan 插桩;ASan 运行库由
|
||
# project.vpj 的 linker.static_libs / dynamic_libs 显式声明(TPV 不注入)
|
||
_asan_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
|
||
|
||
# ============================================================
|
||
# 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: rawptr[memhub.MemBuddy], file_path: str,
|
||
sha1_val: str,
|
||
current_package: str = None,
|
||
declare_only: int = 0,
|
||
temp_dir: str = None) -> rawptr[HandlesTranslator.Translator]:
|
||
"""翻译文件,返回 Translator 对象(调用者负责 dump_ir),None 失败
|
||
|
||
current_package: 当前文件所属包名(用于解析相对导入),None 表示顶级模块
|
||
declare_only: 0=全量翻译(默认),1=仅注册 struct/enum/union(不翻译方法体)
|
||
temp_dir: temp 目录路径(非 None 时在翻译前生成 .vpi 存根文件)
|
||
"""
|
||
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: rawptr[ast.Lexer] = ast.new_lexer(mb)
|
||
ast._lexer_init(lx, src_buf, mb)
|
||
tokens: rawptr[ast.Token] = ast.tokenize(lx)
|
||
tree: rawptr[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
|
||
pyi_pos: sizet = StubConverter._GeneratePyiFromAst(mb, tree, None, 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
|
||
# 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)
|
||
# 同步到 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
|
||
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
|
||
|
||
# 复制路径到可写缓冲区(逐级截断用)
|
||
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
|
||
p2_clen: sizet = len(cc_cmd) + len(cc_flags) + 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 -o %s %s",
|
||
cc_cmd, cc_flags, 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
|
||
cmd_len: sizet = len(cc_cmd) + len(cc_flags) + 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 -o %s %s", cc_cmd, cc_flags, 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)
|
||
cmd_len: sizet = len(cc_cmd) + len(cc_flags) \
|
||
+ 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 -o %s %s",
|
||
cc_cmd, cc_flags, 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] | t.CPtr = 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 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
|
||
|
||
# 收集 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 校验。
|
||
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
|
||
|
||
|
||
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 |