- CallGenMixin: 默认参数快照无条件查询、静态类调用识别、len() 走 __len__ - TypeGenMixin/AssignGenMixin: T | t.CPtr 泛型标量元素宽度修复 - ExprGenMixin: / → __truediv__、指针判空走 icmp null、__doc__ 完整保留(登记表+跨模块符号解析) - BuildPipeline: 翻译后提取模块 docstring(预注册阶段跨模块可用) - Generator: DocKeys/DocValues/DocCount 登记表字段
728 lines
28 KiB
Plaintext
728 lines
28 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import stdio
|
||
import string
|
||
import stdlib
|
||
import memhub
|
||
import w32.fileio as fileio
|
||
import w32.win32file
|
||
import w32.win32base
|
||
import subprocess
|
||
import viperlib
|
||
import ast
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Handles.HandlesTranslator as HandlesTranslator
|
||
import lib.core.Handles.HandlesStruct as HandlesStruct
|
||
import lib.core.Handles.HandlesType as HandlesType
|
||
import lib.core.Handles.HandlesExpr as HandlesExpr
|
||
import lib.core.Handles.HandlesExprOps as HandlesExprOps
|
||
import lib.core.Handles.HandlesExprCall as HandlesExprCall
|
||
import lib.core.Handles.HandlesFunctions as HandlesFunctions
|
||
import lib.core.Handles.HandlesClassDef as HandlesClassDef
|
||
import lib.core.Handles.HandlesImports as HandlesImports
|
||
import lib.core.Handles.HandlesBody as HandlesBody
|
||
import lib.core.Handles.HandlesIf as HandlesIf
|
||
import lib.core.Handles.HandlesWhile as HandlesWhile
|
||
import lib.core.Handles.HandlesFor as HandlesFor
|
||
import lib.core.Handles.HandlesReturn as HandlesReturn
|
||
import lib.core.Handles.HandlesNonlocal as HandlesNonlocal
|
||
import lib.core.Handles.HandlesMatch as HandlesMatch
|
||
import lib.core.Handles.HandlesEnum as HandlesEnum
|
||
import lib.core.Handles.HandlesAnnAssign as HandlesAnnAssign
|
||
import lib.core.Handles.HandlesAugAssign as HandlesAugAssign
|
||
import lib.core.Handles.HandlesVar as HandlesVar
|
||
import lib.core.Handles.HandlesMain as HandlesMain
|
||
import lib.core.StubMerger as StubMerger
|
||
import lib.StubGen.Converter as StubConverter
|
||
import lib.Projectrans.Config as Config
|
||
import lib.core.Gen.Generator as GenGenerator
|
||
|
||
# ============================================================
|
||
# BuildPipeline - 编译管线
|
||
#
|
||
# 负责将 LLVM IR 编译为可执行文件:
|
||
# 1. 写 .ll 文件到 temp 目录
|
||
# 2. 调用 llc 编译 .ll → .obj
|
||
# 3. 调用 clang++ 链接 .obj → .exe
|
||
# ============================================================
|
||
|
||
# 全局 mbuddy 指针
|
||
_mbuddy: memhub.MemBuddy | t.CPtr
|
||
|
||
# 源代码缓冲区大小(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>
|
||
HandlesType.dump_parse_traceback(file_path)
|
||
fb_parse: t.CChar | t.CPtr = VLogger.fmt_buf()
|
||
if fb_parse is not None:
|
||
err_line: t.CInt = ast.get_parse_err_line()
|
||
err_col: t.CInt = 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 = string.strlen(temp_dir)
|
||
pyi_path: str = StubMerger._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.get_global_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.set_global_gen(gen)
|
||
# P2-P5: 注入各 Handles 模块级单例(委托函数经本地 _g_gen 访问,
|
||
# 避免函数体 import 包模块与类/模块名歧义)
|
||
HandlesType.set_global_gen(gen)
|
||
HandlesStruct.set_global_gen(gen)
|
||
HandlesExpr.set_global_gen(gen)
|
||
HandlesExprOps.set_global_gen(gen)
|
||
HandlesExprCall.set_global_gen(gen)
|
||
HandlesFunctions.set_global_gen(gen)
|
||
HandlesClassDef.set_global_gen(gen)
|
||
# P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例
|
||
HandlesImports.set_global_gen(gen)
|
||
HandlesBody.set_global_gen(gen)
|
||
HandlesIf.set_global_gen(gen)
|
||
HandlesWhile.set_global_gen(gen)
|
||
HandlesFor.set_global_gen(gen)
|
||
HandlesReturn.set_global_gen(gen)
|
||
HandlesNonlocal.set_global_gen(gen)
|
||
HandlesMatch.set_global_gen(gen)
|
||
HandlesEnum.set_global_gen(gen)
|
||
HandlesAnnAssign.set_global_gen(gen)
|
||
HandlesAugAssign.set_global_gen(gen)
|
||
HandlesVar.set_global_gen(gen)
|
||
HandlesTranslator.set_global_gen(gen)
|
||
HandlesMain.set_global_gen(gen)
|
||
# 设置当前文件名(供报错使用)
|
||
HandlesType.set_current_file(file_path)
|
||
# 同步到 HandlesImports(import 时 SHA1 检查报错需要当前文件名,循环依赖无法直接读 HandlesType)
|
||
HandlesImports.set_current_file(file_path)
|
||
# 清空调用栈(每个文件翻译开始前重置)
|
||
HandlesType.clear_trace()
|
||
# 模块切换:清空 CDefine 常量表,确保每个模块的 CDefine 常量正确隔离
|
||
HandlesType.clear_cdefine_constants()
|
||
# 模块切换:清空 CTypedef 表,确保每个模块的 CTypedef 正确隔离
|
||
HandlesType.clear_ctypedefs()
|
||
# 命名空间隔离:includes 文件宽松模式(全部可见),用户文件严格模式(仅本地+import)
|
||
strict_mode: int = 1
|
||
if string.strstr(file_path, "includes") is not None:
|
||
strict_mode = 0
|
||
HandlesStruct.reset_visible_structs(mb, strict_mode)
|
||
ret: int = tr.translate(tree)
|
||
if ret != 0:
|
||
return None
|
||
# 提取 docstring(预注册阶段所有 App/includes 文件都被翻译,
|
||
# 保证跨模块 __doc__ 在正式翻译前已登记;查重幂等)
|
||
if gen is not None:
|
||
gen.extract_module_docstrings(tree, sha1_val)
|
||
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 = string.strlen(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(失败),忽略即可
|
||
i: int = 0
|
||
plen: int = path_len
|
||
while i < 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
|
||
i += 1
|
||
|
||
# 创建最终目录
|
||
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 = string.strlen(base_dir)
|
||
sha1_len: t.CSizeT = string.strlen(sha1)
|
||
ext_len: t.CSizeT = string.strlen(ext)
|
||
|
||
if subdir is not None:
|
||
sub_len: t.CSizeT = string.strlen(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
|
||
|
||
# 构造命令: llc -filetype=obj -o {obj_path} ir_path
|
||
cmd_len: t.CSizeT = string.strlen(cc_cmd) + string.strlen(cc_flags) + string.strlen(obj_path) + string.strlen(ir_path) + 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, ir_path)
|
||
|
||
# 用 stdlib.system() 直接在控制台运行,输出直接显示
|
||
# 避免 subprocess 管道捕获丢失输出
|
||
sys_ret: int = stdlib.system(cmd)
|
||
if sys_ret != 0:
|
||
fb: t.CChar | t.CPtr = 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
|
||
|
||
|
||
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:
|
||
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:
|
||
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:
|
||
return out_pos
|
||
|
||
while True:
|
||
fname: str = find_data.cFileName
|
||
if fname is not None:
|
||
fname_len: t.CSizeT = string.strlen(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 = string.strlen(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:
|
||
if 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:
|
||
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
|
||
else:
|
||
break
|
||
|
||
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 = string.strlen(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: t.CChar | t.CPtr = 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 库标志之前,否则链接器无法解析符号依赖
|
||
cmd_len: t.CSizeT = string.strlen(linker_cmd) + string.strlen(obj_path) + string.strlen(linker_flags) + string.strlen(output_dir) + string.strlen(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, linker_flags)
|
||
else:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s -o %s/%s %s",
|
||
linker_cmd, obj_path, output_dir, linker_output,
|
||
linker_flags)
|
||
|
||
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: t.CChar | t.CPtr = 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: t.CChar | t.CPtr = 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
|
||
cmd_len: t.CSizeT = string.strlen(linker_cmd) + obj_paths_len + string.strlen(linker_flags) + string.strlen(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, linker_flags)
|
||
else:
|
||
viperlib.snprintf(cmd, cmd_len, "%s %s -o %s %s",
|
||
linker_cmd, obj_paths, linker_output, linker_flags)
|
||
|
||
# 用 subprocess.run 捕获 clang++ 的 stdout/stderr 输出
|
||
# 之前用 stdlib.system() 无法捕获具体错误(undefined reference 等),
|
||
# 导致 1.txt 中只看到"链接失败,返回码: 1"而看不到未解析符号
|
||
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: t.CChar | t.CPtr = 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 |