import lib.core.Gen.GenApi as GenApi import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HandlesTranslator import lib.Phase.BuildPipeline as BuildPipeline import lib.core.IncludesScanner as IncludesScanner import lib.Stub.StubMerger as StubMerger import lib.Phase.Phase1 as Phase1 import lib.Phase.Phase2 as Phase2 import lib.core.CompilerFactory as CompilerFactory import lib.core.Gen.Generator as GenGenerator import lib.Projectrans.Config as Config import lib.Projectrans.Utils as Utils import lib from stdint import * import stdio import string import stdlib import memhub import argparse import w32.win32console as w32cmd import w32.win32base as w32base import w32.fileio as fileio import sys import ast import llvmlite import subprocess import viperlib import t, c import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect import lib.Phase.Phase1.Phase1Main as Phase1Main # Main Pool 1 GiB POOL_SIZE: t.CDefine = 1073741824 # Permanent memory pool size: 2^29 + 264 = 512MB usable PERM_POOL_SIZE: t.CDefine = 536871176 # Code page: UTF-8 (Windows 65001) CODE_PAGE: t.CDefine = 65001 # Source code buffer size (1MB) SRC_BUF_SIZE: t.CDefine = 1048576 # Version (dev/beta/rc) V_VIPER_VERSION: t.CDefine = "1.0.3-dev.31 2026-07-27 Non-LTS" V_TPV_VERSION: t.CDefine = "0.0.3-dev.428" V_RUNTIME_VERSION: t.CDefine = "0.2.3+19-Non-LTS" V_TPC_COMMIT: t.CDefine = "e376b949c9" V_TPV_COMMIT: t.CDefine = "ab0f37e1a8" V_RUNTIME_COMMIT: t.CDefine = "f087aa88e2" V_TARGET: t.CDefine = "x86_64-w64-windows-gnu" V_LLVM: t.CDefine = "22.1.8" # ============================================================ # main: Command-line entry # ============================================================ def main() -> int: w32cmd.SetConsoleOutputCP(CODE_PAGE) w32cmd.SetConsoleCP(CODE_PAGE) detect_flag: int = 0 cmd_line_d: str = w32base.GetCommandLineA() if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection") is not None: detect_flag = 1 # Initialize the mbuddy memory pool arena: bytes = stdlib.malloc(POOL_SIZE) if arena is None: stdio.printf("FAIL: malloc for arena failed\n") return 1 # Create a global permanent memory pool (perm_pool, 512MB) perm_arena: bytes = stdlib.malloc(PERM_POOL_SIZE) if perm_arena is None: stdio.printf("FAIL: malloc for perm_pool arena failed\n") return 1 mb: rowptr[memhub.MemBuddy] = memhub.MemBuddy(arena, POOL_SIZE, detect_flag) if mb is None: stdio.printf("FAIL: MemBuddy init failed\n") return 1 perm_pool: rowptr[memhub.MemBuddy] = memhub.MemBuddy(perm_arena, PERM_POOL_SIZE, detect_flag) if perm_pool is None: stdio.printf("FAIL: perm_pool init failed\n") return 1 CompilerFactory.factory = CompilerFactory.CompilerFactory(mb, perm_pool) # 设置全局 mbuddy 指针(sys 和 argparse 都需要) sys._mbuddy = mb argparse._mbuddy = mb ast._mbuddy = mb lib._mbuddy = mb Config._mbuddy = mb Utils._mbuddy = mb HandlesTranslator._mbuddy = mb BuildPipeline._mbuddy = mb IncludesScanner._mbuddy = mb StubMerger._mbuddy = mb Phase1._mbuddy = mb Phase2._mbuddy = mb subprocess._mbuddy = mb # hashlib._mbuddy = (mb) VLogger._mbuddy = mb lib._mbuddy = mb # P2: 创建项目级 Generator 单例(Phase1/Phase2/BuildPipeline 全程复用; gen: rowptr[GenGenerator.Generator] = mb.alloc(GenGenerator.Generator.__sizeof__()) if gen is None: stdio.printf("FAIL: Generator alloc failed\n") return 1 gen.__before_init__() gen.__init__(mb) GenGenerator._g_gen = gen # P2-P5: 注入各 Handles 模块级单例(Phase1 阶段即调用其状态函数)。 # 注意:TPV 编译模块级全局带 sha1 前缀(模块隔离),必须逐个模块赋值; # 种子 TPC 时代用裸名 @_g_gen(全局共享),遗漏三个 Handles 模块无感, # 自举后 HT/HE/HEC 委托读各自 _g_gen=None 恒失效(is_global_name 恒 0 误报) GenApi._g_gen = gen # P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例 # 初始化 VLogger 并打印启动日志 VLogger.init_logger() log: VLogger.Logger | t.CPtr = VLogger.logger if log is None: log.error("TransPyV Failed to parse parameters") # 初始化命令行参数(Windows: GetCommandLineA, POSIX: /proc/self/cmdline) sys._init_argv() # 创建参数解析器 parser: argparse.ArgumentParser | t.CPtr = argparse.ArgumentParser("TransPyV", "TransPyV command-line parameter parser", pool=mb) # 注册参数(与 Projectrans.py main() 一致) parser.add_argument("--project", None, argparse.STRING, 0, None, False, argparse.STORE, "Project VPJ path, by default it looks in the current directory") parser.add_argument("--src", None, argparse.STRING, 0, None, False, argparse.STORE, "Source directory") parser.add_argument("--temp", None, argparse.STRING, 0, None, False, argparse.STORE, "Declare a temporary directory for the interface") parser.add_argument("--output", None, argparse.STRING, 0, None, False, argparse.STORE, "Output directory") parser.add_argument("--phase", None, argparse.STRING, 0, None, False, argparse.STORE, "Phase: 1 = Generate statement, 2 = Translate and compile, all = Everything") parser.add_argument("--cc", None, argparse.STRING, 0, None, False, argparse.STORE, "LLVM compiler command") parser.add_argument("--clean", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clean output and temp directories") parser.add_argument("--run", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Run the generated executable after compilation") parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Delete the includes.binary precompiled cache and rebuild all the includes") parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clear .transpyc_cache global cache") parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan; ASan runtime libs must be declared in project.vpj (linker.static_libs)") parser.add_argument("--phase2command", None, argparse.STRING, 0, None, False, argparse.STORE, "Do not run llc/link: write Phase2 llc(opt+llc) and clang link commands to the given .bat path") parser.add_argument("--nowarn-register", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Suppress Phase A dependency WARN") parser.add_argument("--detection", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable MemBuddy allocation detection (0xCDCD markers + record-table probe for out-of-bounds writes; applies to both mb and perm_pool)") parser.add_argument("--show-args", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show all parsed arguments") parser.add_argument("--build-dir", None, argparse.STRING, 0, None, False, argparse.STORE, "Override project.vpj build_dir (default ./.tpv_build; relative paths resolve against the project dir)") parser.add_argument("--no-llvmpeephole", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Disable all local llvmlite peephole passes (T2 store/load forward & ret-zero, T3 no-op bitcast) in FunctionPrint serialization") parser.add_argument("--version", "-v", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show version and exit") parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show help and exit") # 解析命令行参数 args: argparse.ParsedArgs | t.CPtr = parser.parse_args(sys._argc, sys._argv) if args is None: log.error("参数解析失败", "argparse") return 1 if args.get_bool("help"): parser.print_help() return 0 if args.get_bool("version"): stdio.printf("""Viper %s Copyright (C) 2026 Viper Project License MIT GVSDS(TM) Viper SE Runtime Environment and Library (VRTE) %s Viper Frontend Compiler TransPyV %s Author: terminexus@gvsds.com Tpv commit: %s Tpc commit: %s VRTE commit: %s Target: %s """, V_VIPER_VERSION, V_RUNTIME_VERSION, V_TPV_VERSION, V_TPV_COMMIT, V_TPC_COMMIT, V_RUNTIME_COMMIT, V_TARGET, V_LLVM) return 0 # 打印解析结果 # log.banner("TransPyV 参数解析结果") # 字符串参数 project: str = args.get_str("project") src: str = args.get_str("src") temp: str = args.get_str("temp") output: str = args.get_str("output") phase: str = args.get_str("phase") cc: str = args.get_str("cc") build_dir: str = args.get_str("build-dir") _gb_clean: bool = args.get_bool("clean") show_args: bool = args.get_bool("show-args") if show_args: if project is not None: stdio.printf(" --project: %s\n", project) else: stdio.printf(" --project: (未指定)\n") if src is not None: stdio.printf(" --src: %s\n", src) else: stdio.printf(" --src: (未指定)\n") if temp is not None: stdio.printf(" --temp: %s\n", temp) else: stdio.printf(" --temp: (未指定)\n") if output is not None: stdio.printf(" --output: %s\n", output) else: stdio.printf(" --output: (未指定)\n") if phase is not None: stdio.printf(" --phase: %s\n", phase) else: stdio.printf(" --phase: (未指定,默认 all)\n") if cc is not None: stdio.printf(" --cc: %s\n", cc) else: stdio.printf(" --cc: (未指定)\n") if _gb_clean: stdio.printf(" --clean: True\n") else: stdio.printf(" --clean: False\n") if args.get_bool("run"): stdio.printf(" --run: True\n") else: stdio.printf(" --run: False\n") if args.get_bool("rebuild-includes"): stdio.printf(" --rebuild-includes: True\n") else: stdio.printf(" --rebuild-includes: False\n") if args.get_bool("clear-cache"): stdio.printf(" --clear-cache: True\n") else: stdio.printf(" --clear-cache: False\n") stdio.printf("参数解析完成。\n\n") # ASan 模式 # includes 缓存用独立目录 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链 # (asan obj 引用 __asan_init 等符号,非 asan 链接不提供 asan 库 → undefined reference) if args.get_bool("asan"): BuildPipeline._asan_mode = True Config._inc_bin_suffix = ".binary_asan" log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan") # ASan 模式清理 asan includes 缓存(强制重建 asan 插桩 obj) mf_inc_bin_asan: str = Config.get_includes_binary_dir() if mf_inc_bin_asan is not None: n_asan: int = Utils.CleanDir(mf_inc_bin_asan) log.info(f"ASan 模式清理 includes 缓存: {n_asan} 个文件(将重建 asan obj)", "asan") else: BuildPipeline._asan_mode = False Config._inc_bin_suffix = ".binary" # === --phase2command: 只生成命令写入 bat,不执行 llc/链接 === # Phase B/B+ 的 llc(含 ASan opt)与 Phase C 的 clang 链接命令写入指定 # .bat 路径(BuildPipeline._phase2_write_cmd 首次写入 header,后续追加)。 p2c: str = args.get_str("phase2command") if p2c is not None: BuildPipeline._phase2_cmd_path = p2c log.info(f"--phase2command: 不执行 llc/链接,命令写入 {p2c}", "phase2cmd") # --nowarn-register:抑制 Phase A 依赖类 WARN 输出(跨模块 text.ll 未生成,下遍收敛) if args.get_bool("nowarn-register"): VLogger._nowarn_register = 1 log.info("--nowarn-register 已启用:抑制注册类 WARN 输出", "vlogger") # --no-llvmpeephole:关闭全部本地窥孔(T2/T3),FunctionPrint 直接序列化 if args.get_bool("no-llvmpeephole"): llvmlite.G_PeepEnable = 0 log.info("--no-llvmpeephole 已启用:关闭 FunctionPrint 本地窥孔(RenumberSSA 保留)", "peep") # 加载 project.vpj 配置 proj_loaded: int = 0 proj_path: str = project if proj_path is not None: log.banner("工程配置") cfg_ret: int = Config.Load_project_config(proj_path) if cfg_ret == 0: proj_loaded = 1 else: log.warning(f"无法加载 project.vpj: {proj_path}", "config") else: # 尝试默认路径 project.vpj(当前目录和上级目录) proj_path = "project.vpj" if Config.Load_project_config(proj_path) == 0: proj_loaded = 1 else: proj_path = "../project.vpj" if Config.Load_project_config(proj_path) == 0: proj_loaded = 1 if proj_loaded == 1: # 提取 project.vpj 所在目录,将相对路径解析为基于该目录的路径 proj_len: sizet = string.strlen(proj_path) slash_pos: sizet = proj_len for i in range(proj_len): idx: sizet = proj_len - 1 - i ch: char = proj_path[idx] if ch == '/' or ch == '\\': slash_pos = idx break proj_dir: str = "" if slash_pos > 0 and slash_pos < proj_len: proj_dir = stdlib.malloc(slash_pos + 1) if proj_dir is not None: string.memcpy(proj_dir, proj_path, slash_pos) proj_dir[slash_pos] = '\0' # [--build-dir] 命令行覆盖 project.vpj 的 build_dir:在 resolve_paths 前 # 设置,使相对路径仍基于 project_dir 解析(如 ./tpv_build2 → Test/tpv_build2), # 绝对路径(/ 或 \ 开头)由 _join_path 原样保留。 if build_dir is not None: Config.BuildDir = build_dir Config.resolve_paths(proj_dir) Config.print_config() # --asan 要求工程自己在 project.vpj 里声明 ASan 运行库(TPV 不再硬编码 # clang64 的 libclang_rt 路径):clang 没有给 x86_64-w64-windows-gnu 出 # ASan 运行库,只能借用 clang64(MSVC ABI) 的那两份,是否承担这个选择 # 属于工程自身的事,故在此 fail-fast 而不是给个含混的 undefined reference。 if BuildPipeline._asan_mode != 0 and Config.asan_runtime_declared() == 0: log.error("启用 --asan 需要在 project.vpj 的 linker.static_libs 里声明 ASan 运行库:", "asan") log.error(" D:/msys64/clang64/lib/clang/22/lib/windows/libclang_rt.asan_dynamic-x86_64.dll.a", "asan") log.error(" D:/msys64/clang64/lib/clang/22/lib/windows/libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", "asan") log.error("运行时还需要 PATH 里有 D:/msys64/clang64/bin(libclang_rt.asan_dynamic-x86_64.dll)", "asan") return 1 # === --clean: 清理 temp、output 和 includes.binary 目录 === if _gb_clean: log.info("清理临时目录...", "clean") if Config.TempDir is not None: n: int = Utils.CleanDir(Config.TempDir) log.info(f"{Config.TempDir}: 删除 {n} 个文件", "clean") if Config.OutputDir is not None: n2: int = Utils.CleanDir(Config.OutputDir) log.info(f"{Config.OutputDir}: 删除 {n2} 个文件", "clean") # 清理 includes.binary 目录(旧 .obj 文件会导致跳过重新编译) mf_inc_bin_clean: str = Config.get_includes_binary_dir() if mf_inc_bin_clean is not None: n3: int = Utils.CleanDir(mf_inc_bin_clean) log.info(f"{mf_inc_bin_clean}: 删除 {n3} 个文件", "clean") # === --rebuild-includes: 删除 includes.binary 预编译缓存 === # 强制下次编译时重新编译所有 includes(不清 temp/output) if args.get_bool("rebuild-includes"): log.info("重建 includes 缓存...", "rebuild-includes") mf_inc_bin_rb: str = Config.get_includes_binary_dir() if mf_inc_bin_rb is not None: n_rb: int = Utils.CleanDir(mf_inc_bin_rb) log.info(f"{mf_inc_bin_rb}: 删除 {n_rb} 个文件(includes 将重新编译)", "rebuild-includes") # === --clear-cache: 清除 temp 目录缓存(.vpi/.stub.ll/.text.ll) === # 强制下次 Phase1 重新生成 stub(不清 output/includes.binary) if args.get_bool("clear-cache"): log.info("清除 temp 缓存...", "clear-cache") if Config.TempDir is not None: n_cc: int = Utils.CleanDir(Config.TempDir) log.info(f"{Config.TempDir}: 删除 {n_cc} 个文件(.vpi/.stub.ll 缓存)", "clear-cache") # === --phase 参数控制 === # phase=1: 仅 stub 分离(生成 .stub.ll/.text.ll),不编译 # phase=2: 仅 stub 合并 + 编译(跳过 stub 分离) # phase=all: 两者都执行(默认) phase_mode: str = phase if phase_mode is None: phase_mode = "all" do_phase1: int = 0 do_phase2: int = 0 # 注意: 使用 string.strcmp 而非 ==,避免编译器 == 语义 bug if string.strcmp(phase_mode, "1") == 0 or string.strcmp(phase_mode, "all") == 0: do_phase1 = 1 if string.strcmp(phase_mode, "2") == 0 or string.strcmp(phase_mode, "all") == 0: do_phase2 = 1 log.info(f"模式: {phase_mode} (phase1={do_phase1} phase2={do_phase2})", "phase") # === Phase1: 扫描 includes(按需翻译)=== # 对 includes 目录中每个 .vp/.py 文件,若 stub 不存在则翻译并分离 stub if do_phase1 != 0 and Config.IncludesDirs is not None: ph1_temp: str = Config.TempDir if ph1_temp is None: ph1_temp = "." if Phase1Main.RunPhase1(mb, Config.IncludesDirs, ph1_temp, log) != 0: VLogger.error("Phase1 失败,终止编译", "Phase1") return 1 # 如果仅 Phase1(不执行 Phase2),直接退出 if do_phase2 == 0: log.info("Phase1 完成,退出", "phase") return 0 # AST 解析:如果指定了 --src 或 --project,读取文件并解析为 AST 树 src_path: str = args.get_str("src") # 无 project 配置且未指定 --src:无法构建,fail-fast 退出。 # (非工作区执行时 CWD 无 project.vpj,proj_loaded=0;若继续执行, # 未初始化的 Config 字段可能为垃圾指针 → is not None 检查通过 → # 用非法路径 → 崩溃且 ASan 堆栈来不及输出) if proj_loaded == 0 and src_path is None: log.error("未找到 project.vpj(当前目录/上级目录)且未指定 --src,无法构建", "config") argparse.release(args) return 1 # === 多文件项目模式 === # 当 --project 指定但 --src 未指定时,扫描 source_dir 下所有 .vp/.py 文件编译 if src_path is None and project is not None and Config.SourceDir is not None: mf_temp: str = Config.TempDir if Config.TempDir is not None else "." mf_output: str = Config.OutputDir if Config.OutputDir is not None else "." mf_cc: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc" mf_cc_flags: str = Config.CompilerFlags if Config.CompilerFlags is not None else "-filetype=obj -relocation-model=pic" mf_linker: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++" mf_linker_flags: str = Config.LinkerFlags if Config.LinkerFlags is not None else "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition" mf_linker_out: str = Config.LinkerOutput if Config.LinkerOutput is not None else "app.exe" mf_static_libs: str = Config.LinkerStaticLibs mf_dynamic_libs: str = Config.LinkerDynamicLibs mf_includes_bin: str = Config.get_includes_binary_dir() mf_ret: int = Phase2.RunMultiFileProject( mb, Config.SourceDir, mf_temp, mf_output, mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out, mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args, mf_static_libs, mf_dynamic_libs) argparse.release(args) log.success("TransPyV 完成") return mf_ret # === 单文件模式:--src 指定时使用指定文件;--project 但无 SourceDir 时回退 === if src_path is None and project is not None: if Config.SourceDir is not None: # 构造入口路径: SourceDir/main.vp(不再做 vp -> py 后缀识别回退) sd_len: sizet = string.strlen(Config.SourceDir) path_buf: bytes = stdlib.malloc(sd_len + 16) if path_buf is not None: viperlib.snprintf(path_buf, sd_len + 16, "%s/main.vp", Config.SourceDir) src_path = path_buf log.info(f"入口文件: {src_path}", "project") if src_path is not None: log.banner("AST 解析") # 打开文件 f: fileio.File | t.CPtr = fileio.File(src_path, fileio.MODE.R) if f.closed: log.error(f"无法打开文件: {src_path}", "fileio") argparse.release(args) return 1 # read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**; # 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误 if f.size() >= SRC_BUF_SIZE: log.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {src_path}", "fileio") f.close() argparse.release(args) return 1 # 分配源代码缓冲区 src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE) if src_buf is None: log.error("malloc for src_buf failed", "memhub") f.close() argparse.release(args) return 1 # 读取文件内容 bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE) f.close() if bytes_read < 0: log.error(f"读取文件失败 (错误码: {bytes_read})", "fileio") argparse.release(args) return 1 # 添加 null 终止符,确保 strlen 和 SHA1 计算正确 if bytes_read < SRC_BUF_SIZE: src_buf[bytes_read] = 0 # 解析 AST(拆分为词法+语法两阶段,便于调试) ast._init_tables(mb) 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) if tree is None: log.error("AST 解析失败", "ast") argparse.release(args) return 1 # === 翻译 AST → LLVM IR === log.banner("LLVM IR 翻译") tr: HandlesTranslator.Translator | t.CPtr = HandlesTranslator.Translator() result: int = tr.translate(tree) if result == 0: IR_BUF_SIZE: sizet = 262144 ir_buf: bytes = stdlib.malloc(IR_BUF_SIZE) if ir_buf is not None: tr.dump_ir(ir_buf, IR_BUF_SIZE, llvmlite.OUTPUT_FULL) # === 编译管线: .ll → .obj → .exe === log.banner("编译管线") # module_name 用源代码内容的 SHA1(前 16 字符),与 Projectrans.py 一致 # output_name 从文件名推导(如 test.vp -> test.exe),保持可读性 module_name: str = Utils.compute_sha1(mb, src_buf) if module_name is None: VLogger.error("SHA1 计算失败", "main") output_name: str = "test_prog.exe" # 从文件名提取 output_name(去掉路径和 .vp/.py 扩展名) src_len: sizet = string.strlen(src_path) if src_len > 0: # 找到最后一个 / 或 \ 之后的部分 base_start: sizet = src_len i: sizet = src_len while i > 0: i -= 1 ch: char = src_path[i] if ch == '/' or ch == '\\': base_start = i + 1 break if i == 0: base_start = 0 # 找到 .vp/.py 扩展名 base_end: sizet = src_len j: sizet = base_start while j < src_len: if src_path[j] == '.': base_end = j break j += 1 # 输出名 = 文件名 + ".exe" name_len: sizet = base_end - base_start if name_len > 0: out_len: sizet = name_len + 5 output_name = stdlib.malloc(out_len) if output_name is not None: # 手动复制文件名并追加 .exe(viperlib.snprintf 不支持 %.*s,string 无 strcat) string.strncpy(output_name, src_path + base_start, name_len) output_name[name_len] = '.' output_name[name_len + 1] = 'e' output_name[name_len + 2] = 'x' output_name[name_len + 3] = 'e' output_name[name_len + 4] = '\0' # 使用 project.vpj 中的配置 temp_dir: str = Config.TempDir if Config.TempDir is not None else "." output_dir: str = Config.OutputDir if Config.OutputDir is not None else "." # 设置全局 temp_dir(供跨模块 CDefine 查找使用) GenApi.set_temp_dir(temp_dir) cc_cmd: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc" cc_flags: str = "-filetype=obj -relocation-model=pic" linker_cmd: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++" linker_flags: str = "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition" linker_static_libs: str = Config.LinkerStaticLibs linker_dynamic_libs: str = Config.LinkerDynamicLibs # --project 模式且未显式指定 --src 时:使用 config 中的 linker_output(如 TransPyV.exe) # 显式指定 --src 时:使用从文件名推导的 output_name(如 test.exe),避免覆盖正在运行的 exe if project is not None and Config.LinkerOutput is not None and args.get_str("src") is None: linker_output: str = Config.LinkerOutput else: linker_output: str = output_name # 计算 includes.binary 目录路径(链接时附加预编译 .obj) includes_binary_dir: str = Config.get_includes_binary_dir() ir_len: sizet = string.strlen(ir_buf) # (--phase 参数已在配置加载后处理,此处直接使用 do_phase1/do_phase2) # === Phase1 stub 分离:使用 OUTPUT_STUB/TEXT 模式分别生成 stub.ll 和 text.ll === if do_phase1 != 0 and temp_dir is not None and module_name is not None: log.banner("stub 分离") SF_IR_SIZE: sizet = 262144 td_len_sf: sizet = string.strlen(temp_dir) # 保存 stub.ll sf_stub_buf: bytes = stdlib.malloc(SF_IR_SIZE) if sf_stub_buf is not None: tr.dump_ir(sf_stub_buf, SF_IR_SIZE, llvmlite.OUTPUT_STUB) sf_stub_len: sizet = string.strlen(sf_stub_buf) sf_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "stub.ll") if sf_stub_path is not None: sf_f: fileio.File | t.CPtr = fileio.File(sf_stub_path, fileio.MODE.W) if not sf_f.closed: sf_f.write(sf_stub_buf, sf_stub_len) sf_f.close() stdlib.free(sf_stub_path) stdlib.free(sf_stub_buf) # 保存 text.ll sf_text_buf: bytes = stdlib.malloc(SF_IR_SIZE) if sf_text_buf is not None: tr.dump_ir(sf_text_buf, SF_IR_SIZE, llvmlite.OUTPUT_TEXT) sf_text_len: sizet = string.strlen(sf_text_buf) sf_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "text.ll") if sf_text_path is not None: sf_tf: fileio.File | t.CPtr = fileio.File(sf_text_path, fileio.MODE.W) if not sf_tf.closed: sf_tf.write(sf_text_buf, sf_text_len) sf_tf.close() stdlib.free(sf_text_path) stdlib.free(sf_text_buf) # phase=1 模式:仅生成 stub,不执行编译 if do_phase2 == 0: log.success("Phase1 完成(仅 stub 分离,跳过编译)") argparse.release(args) return 0 # === Phase2: 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR === final_ir: bytes = ir_buf final_ir_len: sizet = ir_len if temp_dir is not None and module_name is not None: log.banner("IR 组合") SF_COMBINED_SIZE: sizet = 4194304 # 4MB combined_buf: bytes = stdlib.malloc(SF_COMBINED_SIZE) if combined_buf is not None: combined_len: sizet = StubMergerBuild.BuildCombinedIR(temp_dir, module_name, combined_buf, SF_COMBINED_SIZE) if combined_len > 0: final_ir = combined_buf final_ir_len = combined_len else: stdlib.free(combined_buf) VLogger.error("BuildCombinedIR 失败,终止编译", "stub") br: BuildPipeline.BuildResult | t.CPtr = BuildPipeline.run_pipeline( final_ir, final_ir_len, temp_dir, output_dir, module_name, cc_cmd, cc_flags, linker_cmd, linker_flags, linker_output, includes_binary_dir, linker_static_libs, linker_dynamic_libs ) if br is not None and br.Success == 1: log.success("编译管线完成") log.info(f"输出: {output_dir}/{linker_output}") # --run 模式:执行生成的可执行文件 # phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run if args.get_bool("run") and BuildPipeline._phase2_cmd_path is None: exe_path: bytes = stdlib.malloc(string.strlen(output_dir) + string.strlen(linker_output) + 2) if exe_path is not None: viperlib.snprintf(exe_path, 256, "%s/%s", output_dir, linker_output) log.info(f"执行: {exe_path}") r: subprocess.CompletedProcess | t.CPtr = subprocess.run(exe_path, False, False) if r is not None: log.info(f"退出码: {r.returncode}") else: log.error("编译管线失败", "pipeline") if br is not None and br.ErrorMsg is not None: log.error(f"编译管线失败: {br.ErrorMsg}", "FATAL") sys.exit(1) else: log.error("malloc for IR buffer failed", "memhub") else: log.error("翻译失败", "translator") argparse.release(args) log.success("TransPyV 完成") return 0