From 0efd438bfb277f1ca8cf7c37af6b66d71b52e92a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 25 Aug 2026 14:59:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(main):=20--detection=20=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E5=BC=80=E5=85=B3=E5=BA=94=E7=94=A8=20MemBuddy=20?= =?UTF-8?q?=E5=88=86=E9=85=8D=E6=A3=80=E6=B5=8B=E5=88=B0=E5=8F=8C=E6=B1=A0?= =?UTF-8?q?=20-=20=E5=88=9B=E5=BB=BA=E5=86=85=E5=AD=98=E6=B1=A0=E5=89=8D?= =?UTF-8?q?=E7=94=A8=20GetCommandLineA=20=E6=89=AB=E6=8F=8F=20--detection?= =?UTF-8?q?=EF=BC=88sys.=5Finit=5Fargv=20=E4=BE=9D=E8=B5=96=20mb=20?= =?UTF-8?q?=E4=B8=8D=E8=83=BD=E6=8F=90=E5=89=8D=EF=BC=89=EF=BC=8Cmb(1GB)?= =?UTF-8?q?=20=E4=B8=8E=20perm=5Fpool(512MB)=20=E5=9D=87=E4=BC=A0=20detect?= =?UTF-8?q?ion=20=E5=B8=83=E5=B0=94=EF=BC=9Bdetection=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=8C=BA=E5=A4=A7=E5=B0=8F=E7=A1=AC=E7=BC=96=E7=A0=81=2032MB(m?= =?UTF-8?q?emhub=20MEMBUDDY=5FDET=5FRECORD=5FSIZE)=EF=BC=9Bargparse=20?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=20--detection=20=E5=B8=AE=E5=8A=A9=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TransPyV/App/main.vp | 697 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 697 insertions(+) create mode 100644 TransPyV/App/main.vp diff --git a/TransPyV/App/main.vp b/TransPyV/App/main.vp new file mode 100644 index 0000000..cbe87fe --- /dev/null +++ b/TransPyV/App/main.vp @@ -0,0 +1,697 @@ +import lib.core.Gen.GenApi as GenApi +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.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.30 2026-07-27 Non-LTS" +V_TPV_VERSION: t.CDefine = "0.0.3-dev.230" +V_RUNTIME_VERSION: t.CDefine = "0.2.2+4-Non-LTS" +V_TPC_COMMIT: t.CDefine = "1dc23ffdde" +V_TPV_COMMIT: t.CDefine = "f4fee79d4d" +V_RUNTIME_COMMIT: t.CDefine = "4ce6095359" +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) + + # [DET] 分配检测开关:在创建内存池前扫描命令行(sys._init_argv 依赖 mb, + # 不能提前;GetCommandLineA 直接读原始命令行,不含任何 pool 分配)。 + # --detection 开启后 mb/perm_pool 均启用前后 0xCDCD 标记 + 记录表探测 + # 越界写(定位"存活块被部分覆盖"的写入者,如 KwEntry cargs)。 + 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: memhub.MemBuddy | t.CPtr = memhub.MemBuddy(arena, POOL_SIZE, detect_flag) + if mb is None: + stdio.printf("FAIL: MemBuddy init failed\n") + return 1 + + perm_pool: memhub.MemBuddy | t.CPtr = 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.set_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: GenGenerator.Generator | t.CPtr = 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 + HandlesTranslator._g_gen = gen + HandlesExpr._g_gen = gen + HandlesExprCall._g_gen = gen + # P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例 + + # 初始化 VLogger 并打印启动日志 + log: VLogger.Logger | t.CPtr = VLogger.get_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 then link -fsanitize=address") + 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("--enable-debug-log", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable LISTDBG diagnostic output (list.__init__/append print)") + 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("--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: + if log is not 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 + + # 打印解析结果 + #if log is not None: + # 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") + _gb_clean: INT = args.get_bool("clean") + #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" + if log is not None: + log.info("ASan 模式已启用(opt -passes=asan + libclang_rt)", "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) + if log is not None: + fb_asan: str = VLogger.fmt_buf() + if fb_asan is not None: + viperlib.snprintf(fb_asan, 1024, "ASan 模式清理 includes 缓存: %d 个文件(将重建 asan obj)", n_asan) + log.info(fb_asan, "asan") + else: + BuildPipeline._asan_mode = False + Config._inc_bin_suffix = ".binary" + + # 调试日志开关:--enable-debug-log 开启 list.__init__/append 的 LISTDBG 诊断 + # (输出量大,默认静默;仅排查 list 头覆盖等运行时问题时开启) + if args.get_bool("enable-debug-log"): + memhub.set_debug_log(1) + if log is not None: + log.info("调试日志已启用(LISTDBG: list.__init__/append 诊断输出)", "dbg") + + # === --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 + if log is not None: + fb_p2c: str = VLogger.fmt_buf() + if fb_p2c is not None: + viperlib.snprintf(fb_p2c, 1024, "--phase2command: 不执行 llc/链接,命令写入 %s", p2c) + log.info(fb_p2c, "phase2cmd") + + # --nowarn-register:抑制"外部函数返回类型未登记"类 WARN 输出 + if args.get_bool("nowarn-register"): + VLogger.set_nowarn_register(1) + if log is not None: + log.info("--nowarn-register 已启用:抑制注册类 WARN 输出", "vlogger") + + # 加载 project.vpj 配置 + proj_loaded: int = 0 + proj_path: str = project + if proj_path is not None: + if log is not None: + log.banner("工程配置") + cfg_ret: int = Config.Load_project_config(proj_path) + if cfg_ret == 0: + proj_loaded = 1 + else: + if log is not None: + warn_buf: str = stdlib.malloc(256) + if warn_buf is not None: + viperlib.snprintf(warn_buf, 256, "无法加载 project.vpj: %s", proj_path) + log.warning(warn_buf, "config") + stdlib.free(warn_buf) + 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: t.CSizeT = string.strlen(proj_path) + slash_pos: t.CSizeT = proj_len + for i in range(proj_len): + idx: t.CSizeT = proj_len - 1 - i + ch: t.CChar = 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' + Config.resolve_paths(proj_dir) + Config.print_config() + + # === --clean: 清理 temp、output 和 includes.binary 目录 === + if _gb_clean: + if log is not None: + log.info("清理临时目录...", "clean") + if Config.TempDir is not None: + n: int = Utils.CleanDir(Config.TempDir) + if log is not None: + fb: str = VLogger.fmt_buf() + if fb is not None: + viperlib.snprintf(fb, 1024, "%s: 删除 %d 个文件", Config.TempDir, n) + log.info(fb, "clean") + if Config.OutputDir is not None: + n2: int = Utils.CleanDir(Config.OutputDir) + if log is not None: + fb2: str = VLogger.fmt_buf() + if fb2 is not None: + viperlib.snprintf(fb2, 1024, "%s: 删除 %d 个文件", Config.OutputDir, n2) + log.info(fb2, "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) + if log is not None: + fb3: str = VLogger.fmt_buf() + if fb3 is not None: + viperlib.snprintf(fb3, 1024, "%s: 删除 %d 个文件", mf_inc_bin_clean, n3) + log.info(fb3, "clean") + + # === --rebuild-includes: 删除 includes.binary 预编译缓存 === + # 强制下次编译时重新编译所有 includes(不清 temp/output) + if args.get_bool("rebuild-includes"): + if log is not None: + 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) + if log is not None: + fb_rb: str = VLogger.fmt_buf() + if fb_rb is not None: + viperlib.snprintf(fb_rb, 1024, "%s: 删除 %d 个文件(includes 将重新编译)", mf_inc_bin_rb, n_rb) + log.info(fb_rb, "rebuild-includes") + + # === --clear-cache: 清除 temp 目录缓存(.vpi/.stub.ll/.text.ll) === + # 强制下次 Phase1 重新生成 stub(不清 output/includes.binary) + if args.get_bool("clear-cache"): + if log is not None: + log.info("清除 temp 缓存...", "clear-cache") + if Config.TempDir is not None: + n_cc: int = Utils.CleanDir(Config.TempDir) + if log is not None: + fb_cc: str = VLogger.fmt_buf() + if fb_cc is not None: + viperlib.snprintf(fb_cc, 1024, "%s: 删除 %d 个文件(.vpi/.stub.ll 缓存)", Config.TempDir, n_cc) + log.info(fb_cc, "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 + if log is not None: + fb3: str = VLogger.fmt_buf() + if fb3 is not None: + viperlib.snprintf(fb3, 1024, "模式: %s (phase1=%d phase2=%d)", phase_mode, do_phase1, do_phase2) + log.info(fb3, "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: + if log is not None: + 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: + if log is not 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_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) + argparse.release(args) + if log is not None: + 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,回退 SourceDir/main.py + sd_len: t.CSizeT = string.strlen(Config.SourceDir) + path_buf: bytes = stdlib.malloc(sd_len + 16) + if path_buf is not None: + # 优先尝试 main.vp + viperlib.snprintf(path_buf, sd_len + 16, "%s/main.vp", Config.SourceDir) + ef: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R) + if ef.closed: + # .vp 不存在,回退 main.py + viperlib.snprintf(path_buf, sd_len + 16, "%s/main.py", Config.SourceDir) + else: + ef.close() + src_path = path_buf + if log is not None: + fb4: str = VLogger.fmt_buf() + if fb4 is not None: + viperlib.snprintf(fb4, 1024, "入口文件: %s", src_path) + log.info(fb4, "project") + if src_path is not None: + if log is not None: + log.banner("AST 解析") + # 打开文件 + f: fileio.File | t.CPtr = fileio.File(src_path, fileio.MODE.R) + if f.closed: + if log is not None: + fb5: str = VLogger.fmt_buf() + if fb5 is not None: + viperlib.snprintf(fb5, 1024, "无法打开文件: %s", src_path) + log.error(fb5, "fileio") + argparse.release(args) + return 1 + # 分配源代码缓冲区 + src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE) + if src_buf is None: + if log is not 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: + if log is not None: + fb6: str = VLogger.fmt_buf() + if fb6 is not None: + viperlib.snprintf(fb6, 1024, "读取文件失败 (错误码: %d)", bytes_read) + log.error(fb6, "fileio") + argparse.release(args) + return 1 + # 添加 null 终止符,确保 strlen 和 SHA1 计算正确 + if bytes_read < SRC_BUF_SIZE: + src_buf[bytes_read] = 0 + else: + src_buf[SRC_BUF_SIZE - 1] = 0 + if log is not None: + log.info("文件读取完成", "fileio") + fb7: str = VLogger.fmt_buf() + if fb7 is not None: + viperlib.snprintf(fb7, 1024, "读取 %d 字节", bytes_read) + log.info(fb7, "fileio") + # 解析 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: + if log is not None: + log.error("AST 解析失败", "ast") + argparse.release(args) + return 1 + + # === 翻译 AST → LLVM IR === + if log is not None: + log.banner("LLVM IR 翻译") + tr: HandlesTranslator.Translator | t.CPtr = HandlesTranslator.Translator() + result: int = tr.translate(tree) + if result == 0: + IR_BUF_SIZE: t.CSizeT = 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 === + if log is not None: + 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: t.CSizeT = string.strlen(src_path) + if src_len > 0: + # 找到最后一个 / 或 \ 之后的部分 + base_start: t.CSizeT = src_len + i: t.CSizeT = src_len + while i > 0: + i -= 1 + ch: t.CChar = src_path[i] + if ch == '/' or ch == '\\': + base_start = i + 1 + break + if i == 0: + base_start = 0 + # 找到 .vp/.py 扩展名 + base_end: t.CSizeT = src_len + j: t.CSizeT = base_start + while j < src_len: + if src_path[j] == '.': + base_end = j + break + j += 1 + # 输出名 = 文件名 + ".exe" + name_len: t.CSizeT = base_end - base_start + if name_len > 0: + out_len: t.CSizeT = 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" + # --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: t.CSizeT = 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: + if log is not None: + log.banner("stub 分离") + SF_IR_SIZE: t.CSizeT = 262144 + td_len_sf: t.CSizeT = 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: t.CSizeT = 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: t.CSizeT = 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: + if log is not None: + log.success("Phase1 完成(仅 stub 分离,跳过编译)") + argparse.release(args) + return 0 + + # === Phase2: 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR === + final_ir: bytes = ir_buf + final_ir_len: t.CSizeT = ir_len + if temp_dir is not None and module_name is not None: + if log is not None: + log.banner("IR 组合") + SF_COMBINED_SIZE: t.CSizeT = 4194304 # 4MB + combined_buf: bytes = stdlib.malloc(SF_COMBINED_SIZE) + if combined_buf is not None: + combined_len: t.CSizeT = 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 + ) + if br is not None and br.Success == 1: + if log is not None: + log.success("编译管线完成") + fb8: str = VLogger.fmt_buf() + if fb8 is not None: + viperlib.snprintf(fb8, 1024, "输出: %s/%s", output_dir, linker_output) + log.info(fb8) + # --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) + if log is not None: + fb9: str = VLogger.fmt_buf() + if fb9 is not None: + viperlib.snprintf(fb9, 1024, "执行: %s", exe_path) + log.info(fb9, "run") + r: subprocess.CompletedProcess | t.CPtr = subprocess.run(exe_path, False, False) + if r is not None: + if log is not None: + fb10: str = VLogger.fmt_buf() + if fb10 is not None: + viperlib.snprintf(fb10, 1024, "退出码: %d", r.returncode) + log.info(fb10, "run") + else: + if log is not None: + log.error("编译管线失败", "pipeline") + if br is not None and br.ErrorMsg is not None: + fb11: str = VLogger.fmt_buf() + if fb11 is not None: + viperlib.snprintf(fb11, 1024, "编译管线失败: %s", br.ErrorMsg) + log.error(fb11, "FATAL") + sys.exit(1) + else: + if log is not None: + log.error("malloc for IR buffer failed", "memhub") + else: + if log is not None: + log.error("翻译失败", "translator") + + argparse.release(args) + if log is not None: + log.success("TransPyV 完成") + return 0