Files
ViperC/App/lib/Phase/BuildPipeline.vp
T
TermiNexus a12dcafed2 fix(tpv): 泛型类型参数 SHA1 规范化 + 自举链接修复
- 类型参数统一 "{定义模块SHA1}.{Class}"(ast.AST/AST → f467978339422f32.AST),
  消除跨模块特化名分裂导致的 undefined symbol(list[AST|t.CPtr].set 根因)
- TypeGenResolve 新增 _norm_type_arg_name/_is_sha1_prefixed/_concat_sha1_class;
  resolve_annotation_type 兼容 "{16hex}.Class" Attribute 形态(跳过
  imported_modules 检查,优先完整名命中定义侧 struct)
- CallGenCtor._extract_type_args_from_slice / extract_class_name_from_annotation
  新增 from_imports 参数,8 处调用方传 trans._from_imports 保持注册/查找一致
- 修复模块级函数链式调用推断(get_perm_pool() 联合返回 → .alloc() 裸名 alloc)
- 跨模块方法 declare self 改为类指针 %"sha1.Class"*,调用侧按形参 coerce bitcast
- 撤销泛型特化双 SHA1 前缀,恢复单前缀(模板 SHA1)
- 新增 --phase2command xxx.bat:不执行 llc/链接,命令写入 bat
2026-08-23 12:00:04 +08:00

1020 lines
43 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.Handles.HandlesTranslator as HandlesTranslator
import lib.core.Handles.HandlesExpr as HandlesExpr
import lib.core.Handles.HandlesExprCall as HandlesExprCall
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.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 | t.CPtr
# ASan 模式(--asan):对 .ll 先 opt -passes=asan 插桩,链接加 -fsanitize=address
_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: t.CSizeT = 262144
# ============================================================
# TranslateFileGetTrans - 翻译单个文件,返回 Translator 对象
#
# 读取文件 → AST 解析 → .vpi 生成 → LLVM IR 翻译,返回 Translator 对象供
# 调用者 dump_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 | t.CPtr, file_path: str,
sha1_val: str,
current_package: str = None,
declare_only: int = 0,
temp_dir: str = None) -> HandlesTranslator.Translator | t.CPtr:
"""翻译文件,返回 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: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R)
if f.closed: 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
else:
src_buf[SRC_BUF_SIZE - 1] = 0
lx: ast.Lexer | t.CPtr = ast.new_lexer(mb)
ast._lexer_init(lx, src_buf, mb)
tokens: ast.Token | t.CPtr = ast.tokenize(lx)
tree: ast.AST | t.CPtr = 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)
fb_parse: str = VLogger.fmt_buf()
if fb_parse is not None:
err_line: int = ast.get_parse_err_line()
err_col: int = ast.get_parse_err_col()
viperlib.snprintf(fb_parse, 1024, "解析失败: %s (line %d col %d)", file_path, err_line, err_col)
VLogger.error(fb_parse, "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: t.CSizeT = StubConverter._GeneratePyiFromAst(mb, tree, None, pyi_buf, PYI_BUF_SIZE)
if pyi_pos > 0:
# 写入 {sha1}.vpi 到 temp_dir(切片路径)
td_len_vpi: t.CSizeT = len(temp_dir)
pyi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpi, sha1_val, "vpi")
if pyi_path is not None:
pf: fileio.File | t.CPtr = fileio.File(pyi_path, fileio.MODE.W)
if not pf.closed:
pf.write(pyi_buf, pyi_pos)
pf.close()
stdlib.free(pyi_path)
stdlib.free(pyi_buf)
tr: HandlesTranslator.Translator | t.CPtr = 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: GenGenerator.Generator | t.CPtr = 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 读
# 0xffffffffffffffff 崩溃),docstring 提取与翻译状态无关,提前安全。
if gen is not None:
gen.ExprGenCls.extract_module_docstrings(tree, sha1_val)
ret: int = tr.translate(tree)
if ret != 0:
fb_tf: str = VLogger.fmt_buf()
if fb_tf is not None:
viperlib.snprintf(fb_tf, 1024, "文件翻译失败: %s", file_path)
VLogger.error(fb_tf, "Translate")
return None
return tr
class BuildResult:
Success: t.CInt
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: t.CSizeT = 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 - 构建切片路径并确保目录存在
#
# 根据 Config.Sha1SliceLevel 将文件分散到 SHA1 前缀子目录:
# level=0 → {base_dir}/{sha1}.{ext}
# level=1 → {base_dir}/b7/{sha1}.{ext}
# level=2 → {base_dir}/b7/90/{sha1}.{ext}
#
# 自动调用 ensure_dir 创建子目录。
#
# Args:
# base_dir: 基础目录(如 temp_dir)
# sha1: SHA1 字符串
# ext: 文件扩展名(如 "stub.ll")
#
# Returns:
# 完整文件路径(mbuddy 分配),None 失败
# ============================================================
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: t.CSizeT = len(base_dir)
sha1_len: t.CSizeT = len(sha1)
ext_len: t.CSizeT = len(ext)
if subdir is not None:
sub_len: t.CSizeT = 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: t.CSizeT = 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: t.CSizeT, 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: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.W)
if f.closed: return 1
written: t.CInt64T = 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: t.CSizeT = 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: t.CSizeT = 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: t.CSizeT = 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:
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "opt ASan 插桩失败 (module=%s)", module_name)
VLogger.error(fb, "ASAN")
return 1
compile_input = asan_path
# 构造命令: llc -filetype=obj -o {obj_path} ir_path
cmd_len: t.CSizeT = 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)
# 用 stdlib.system() 直接在控制台运行,输出直接显示
# 避免 subprocess 管道捕获丢失输出
sys_ret: int = stdlib.system(cmd)
if sys_ret != 0:
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "编译失败 (module=%s, cmd=%s)", module_name, cmd)
VLogger.error(fb, "LLC")
return 1
return 0
# ============================================================
# llc 并行编译(线程异步,Phase B 多模块同时跑 llc)
#
# 线程安全约束:
# - LlcTask 及命令串一律 stdlib.malloc:主池 mb 会被 reset_to(mark2)
# 回收,线程运行期间字符串悬空 → 概率崩溃
# - worker 只调 stdlib.system(),不做任何池分配(MemBuddy 分配非线程安全)
# - 依赖 CRT system() 的多线程安全性(ThreadingTest 已验证)
# ============================================================
# 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 失败
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: t.CSizeT = len(module_name)
task.ModuleSha1 = stdlib.malloc(sl + 1)
if task.ModuleSha1 is None:
return 1
string.strcpy(task.ModuleSha1, module_name)
# llc 命令
cmd_len: t.CSizeT = len(cc_cmd) + len(cc_flags) \
+ len(obj_path) + len(ir_path) + 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, ir_path)
# ASan 模式:opt 插桩命令(worker 先跑,失败即置 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: t.CSizeT = 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)
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 指定。
"""
if _phase2_cmd_path is None or cmd is None:
return
f: fileio.File | t.CPtr = 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 线程安全。
"""
if cmd is None:
return -1
cl_len: t.CSizeT = 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 语义)
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)))
stdlib.free(cmd_line)
if ok == 0:
return -1
win32sync.WaitForSingleObject(pi.hProcess, w32.win32base.INFINITE)
exit_code: t.CUInt32T = 0
win32process.GetExitCodeProcess(pi.hProcess, t.CPtr(c.Addr(exit_code)))
w32.win32base.CloseHandle(pi.hProcess)
w32.win32base.CloseHandle(pi.hThread)
return t.CInt(exit_code)
def _llc_task_worker(arg: t.CPtr) -> t.CPtr:
"""llc 线程入口:只调 stdlib.system(),不做任何分配(线程安全)
Result 仅在命令链执行完后写入(初始 -1;join 超时保持 -1 → 报失败)"""
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.Result = 1
return t.CPtr(0)
llc_rc: int = _run_cmd_wait(task.LlcCmd)
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)(t.CVoid(
t.CUInt64T(tasks) + t.CSizeT(pi2) * LlcTask.__sizeof__(), t.CPtr))
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)
fb_p2: str = VLogger.fmt_buf()
if fb_p2 is not None:
viperlib.snprintf(fb_p2, 1024, "phase2command 模式:%d 条 llc 命令已写入 %s", task_count, _phase2_cmd_path)
VLogger.info(fb_p2, "phase2cmd")
return 0
if max_threads <= 0:
max_threads = 1
# 线程句柄数组:list[str](str=bytes=i8*,elem_size=8 显式)存句柄值。
# 此前 list[t.CPtr] 特化修复后 append 仍崩(pool 有效、elem=8 下读零页,
# list[ptr] append 的 store 与 t.CPtr 参数类型生成待查);list[str] 全库
# 验证稳定(append/get 8 字节指针)。start() 后立即取 handle 存入,
# join/close 直接对句柄操作,不依赖 Thread 对象存续。
handles: list[str] | t.CPtr = list[str](_mbuddy, 8)
if handles is None:
VLogger.error("run_llc_parallel: 线程句柄数组分配失败", "PhaseB")
return 1
slot_size: t.CSizeT = 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)(t.CVoid(
t.CUInt64T(tasks) + t.CSizeT(batch_start + j) * slot_size, t.CPtr))
th: vthreading.Thread | t.CPtr = 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: t.CUInt32T = 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)(t.CVoid(
t.CUInt64T(tasks) + t.CSizeT(batch_start + j) * slot_size, t.CPtr))
if tsk2 is not None and tsk2.Result != 0:
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "llc 编译失败: %s (cmd=%s)",
(tsk2.ModuleSha1 if tsk2.ModuleSha1 is not None else "?"),
(tsk2.LlcCmd if tsk2.LlcCmd is not None else "?"))
VLogger.error(fb, "LLC")
ret = 1
return ret
def _CollectObjFilesImpl(base_dir: str, bd_len: t.CSizeT,
out_buf: bytes, out_pos: t.CSizeT, out_size: t.CSizeT) -> t.CSizeT:
"""递归扫描 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: t.CSizeT = 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: t.CSizeT = 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: t.CSizeT = 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: t.CSizeT) -> t.CSizeT:
"""递归扫描 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: t.CSizeT = len(includes_binary_dir)
out_pos: t.CSizeT = _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) -> 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)
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: t.CSizeT = 32768
extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE)
if extra_objs is None:
return 1
extra_len: t.CSizeT = 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:
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "附加 %d 字节的 includes.binary .obj 文件", extra_len)
VLogger.info(fb, "link")
else:
fb = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "includes.binary 无 .obj 文件: %s", includes_binary_dir)
VLogger.warning(fb, "link")
# 构造命令: clang++ {obj_path} extra_objs -o {output_dir}/{linker_output} linker_flags
# 注意: .obj 文件必须在 -l 库标志之前,否则链接器无法解析符号依赖
# ASan 模式:mingw64 clang 的 -fsanitize=address 在 mingw64/lib 下找不到
# asan 运行库(实际在 clang64),改为显式链接 clang64 的 libclang_rt
# (DLL 版,运行时需 D:/msys64/clang64/bin 在 PATH)
use_flags: str = linker_flags
if _asan_mode != 0:
fl_len: t.CSizeT = len(linker_flags) + 256
use_flags = _mbuddy.alloc(fl_len)
if use_flags is None:
return 1
viperlib.snprintf(use_flags, fl_len,
"%s -L D:/msys64/clang64/lib/clang/22/lib/windows -l:libclang_rt.asan_dynamic-x86_64.dll.a -l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a",
linker_flags)
cmd_len: t.CSizeT = len(linker_cmd) + len(obj_path) + len(use_flags) + len(output_dir) + len(linker_output) + extra_len + 128
cmd: bytes = _mbuddy.alloc(cmd_len)
if cmd is None:
return 1
if 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: subprocess.CompletedProcess | t.CPtr = subprocess.run(cmd, True, True)
if result is None:
VLogger.error("subprocess.run 返回 None", "link")
return 1
if result.returncode != 0:
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "链接失败,返回码: %d", result.returncode)
VLogger.error(fb, "link")
fb = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "命令: %s", cmd)
VLogger.error(fb, "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 compile_module_to_obj(ir_buf: bytes, ir_len: t.CSizeT,
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:
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "写 .ll 失败: %s", module_name)
VLogger.error(fb, "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:
fb = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "llc 编译失败: %s", module_name)
VLogger.error(fb, "compile")
return 1
return 0
def link_objs_to_exe(obj_paths: str, obj_paths_len: t.CSizeT,
linker_cmd: str, linker_flags: str, linker_output: str,
includes_binary_dir: str) -> 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)
Returns:
0 成功,非 0 失败
"""
if obj_paths is None or obj_paths_len == 0 or linker_cmd is None:
return 1
# 收集 includes.binary 的 .obj 文件路径
EXTRA_BUF_SIZE: t.CSizeT = 32768
extra_objs: bytes = _mbuddy.alloc(EXTRA_BUF_SIZE)
if extra_objs is None:
return 1
extra_len: t.CSizeT = 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 模式:显式链接 clang64 的 libclang_rt(同 link_obj_to_exe)
use_flags: str = linker_flags
if _asan_mode != 0:
fl_len: t.CSizeT = len(linker_flags) + 256
use_flags = _mbuddy.alloc(fl_len)
if use_flags is None:
return 1
viperlib.snprintf(use_flags, fl_len,
"%s -L D:/msys64/clang64/lib/clang/22/lib/windows -l:libclang_rt.asan_dynamic-x86_64.dll.a -l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a",
linker_flags)
cmd_len: t.CSizeT = len(linker_cmd) + obj_paths_len + len(use_flags) + len(linker_output) + extra_len + 128
cmd: bytes = _mbuddy.alloc(cmd_len)
if cmd is None:
return 1
if 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: subprocess.CompletedProcess | t.CPtr = 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)
fb: str = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "链接失败,返回码: %d, 命令: %s", result.returncode, cmd)
VLogger.error(fb, "link")
return 1
return 0
def run_pipeline(ir_buf: bytes, ir_len: t.CSizeT,
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) -> 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)
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)
ret = link_obj_to_exe(output_dir, module_name, linker_cmd, linker_flags, linker_output,
includes_binary_dir)
if ret != 0:
result.Success = 0
result.ErrorMsg = "链接失败"
return result
result.Success = 1
return result