From a2874a14e630fc0644f3d3d4ae9ac2b7c1e49dc1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 5 Oct 2026 09:22:58 +0800 Subject: [PATCH] =?UTF-8?q?chore(tpv):=20=E7=A7=BB=E9=99=A4=20--allow-mult?= =?UTF-8?q?iple-definition=20=E5=85=9C=E5=BA=95=E5=B9=B6=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=BC=BA=E7=9C=81=E9=93=BE=E6=8E=A5=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main.vp 的缺省 linker flags 与 ViperBuild 内置 _c_linker 默认值去掉 -Wl,--allow-multiple-definition,与 project.vpj 同口径;Phase2 三处引用该 flag 的注释改为描述链接器选 main / 链接前按路径去重的实际机制。重建验证:跨模块内联优化完成、链接成功 139.17s。 --- App/lib/Phase/Phase2/__init__.vp | 7 +- App/lib/core/builtin/ViperBuild.vp | 2 +- App/main.vp | 986 ++++++++++++++--------------- repl.vpj | 2 +- 4 files changed, 499 insertions(+), 498 deletions(-) diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index 017d78b..a8bde7c 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -1054,7 +1054,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou obj_paths[0] = '\0' obj_pos: sizet = 0 # main_obj_path: 存放定义了用户 main 的 .obj 路径(链接时放在最前面, - # 避免 --allow-multiple-definition 选择了其他模块的 wrapper main) + # 避免链接器选中其他模块的 wrapper main) main_obj_path: bytes = stdlib.malloc(512) if main_obj_path is None: return 1 main_obj_path[0] = '\0' @@ -1478,7 +1478,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou return 1 final_obj_paths[0] = '\0' fop_pos: sizet = 0 - # main_obj_path 放在最前面(确保 --allow-multiple-definition 选择用户 main) + # main_obj_path 放在最前面(确保链接器优先选中用户 main) if main_obj_path[0] != '\0': mlen: sizet = string.strlen(main_obj_path) string.strcpy(final_obj_paths, main_obj_path) @@ -1486,7 +1486,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou # 合并 Phase B/B+ 编译的 App 非 main 模块 .obj(obj_paths,空格分隔)。 # final_obj_paths 需含 main_obj + reachable includes + App 依赖文件的 .obj,否则 App import 的模块不入链 → undefined symbol。 - # obj_paths 同时含 Phase B+ 本次新编译的 includes obj(与下方 reachable 收集重复,--allow-multiple-definition 容忍,仅本次编译的少量文件)。 + # obj_paths 同时含 Phase B+ 本次新编译的 includes obj(与下方 reachable 收集重复, + # 仅本次编译的少量文件;链接前按路径去重,见 BuildPipeline.link_objs_to_exe)。 if obj_pos > 0: if fop_pos + obj_pos + 1 < OBJ_PATHS_SIZE + 512: if fop_pos > 0: diff --git a/App/lib/core/builtin/ViperBuild.vp b/App/lib/core/builtin/ViperBuild.vp index ee5836b..3adb875 100644 --- a/App/lib/core/builtin/ViperBuild.vp +++ b/App/lib/core/builtin/ViperBuild.vp @@ -1072,7 +1072,7 @@ def _FinalizeCfg(entry_path: str) -> int: # 内置默认 = 本仓 TransPyV/project.vpj 那份**已验证可用**的链接参数。 # 注意:不要带 -lgcc —— msys64/mingw64 + lld 下没有 libgcc, # 会以 "lld: error: unable to find library -lgcc" 直接链接失败。 - _c_linker = _DupStd("clang++ -fuse-ld=lld -lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -Wl,--allow-multiple-definition -Wl,--stack,0x1000000 -Wl,--no-insert-timestamp -Wl,--export-all-symbols -g") + _c_linker = _DupStd("clang++ -fuse-ld=lld -lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -Wl,--stack,0x1000000 -Wl,--no-insert-timestamp -Wl,--export-all-symbols -g") if _c_output is None: _c_output = _DeriveOutputFromEntry(entry_path) # ---- 入口文件必须位于 source 之下 ---- diff --git a/App/main.vp b/App/main.vp index fee80ee..021c04f 100644 --- a/App/main.vp +++ b/App/main.vp @@ -1,493 +1,493 @@ -import lib.core.Gen.GenApi as GenApi -import lib.core.VLogger as VLogger -import lib.core.Repl as Repl -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.core.builtin.ViperBuild as ViperBuild -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 mlirlite -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 -define POOL_SIZE = 1073741824 -# Permanent memory pool size: 2^29 + 264 = 512MB usable, then change to 1 GiB -define PERM_POOL_SIZE = 1073741824 -# Code page: UTF-8 (Windows 65001) -define CODE_PAGE = 65001 -# Source code buffer size (1MB) -define SRC_BUF_SIZE = 1048576 -# Version (dev/beta/rc) -V_TPV_VERSION: __attribute__(section(".PRVERSN")) []char = "0.0.5-dev.479" -define V_VIPER_VERSION = "1.0.5-dev.970 2026-09-28 Non-LTS" -define V_RUNTIME_VERSION = "0.2.3+19-Non-LTS" -define V_TPC_COMMIT = "e376b949c9" -define V_TPV_COMMIT = "ab0f37e1a8" -define V_RUNTIME_COMMIT = "f087aa88e2" -define V_TARGET = "x86_64-w64-windows-gnu" -define V_LLVM = "22.1.8" -# --repl 模板工程查找缓冲(repl.vpj 命中路径 / 未命中诊断文本) -define REPL_PROJ_PATH_CAP = 1024 -define REPL_PROJ_DIAG_CAP = 2048 - -static a: int -# ============================================================ -# 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 - - # --detection-deep:重诊断(每次 alloc 探测 + 周期全表扫描,极慢,默认关);须在 MemBuddy 构造前用原始命令行 strstr 取值 - deep_detect_flag: int = 0 - if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection-deep") is not None: deep_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* = memhub.MemBuddy(arena, POOL_SIZE, detect_flag, deep_detect_flag) - if mb is None: - stdio.printf("FAIL: MemBuddy init failed\n") - return 1 - - perm_pool: memhub.MemBuddy* = memhub.MemBuddy(perm_arena, PERM_POOL_SIZE, detect_flag, deep_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: rawptr[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 阶段即调用其状态函数);模块级全局带 sha1 前缀(模块隔离),必须逐个模块赋值 - GenApi._g_gen = gen - # P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例 - - # 初始化 VLogger 并打印启动日志 - VLogger.init_logger() - log: VLogger.Logger = 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 = 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("--traceback", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Traceback mode: force-compile includes/traceback.vp + Install() ctor, llc -frame-pointer=all, link -ldbghelp, export .stackmap") - 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)") - # 仅注册以便 argparse 接受该标志(取值不读这里:MemBuddy 在本函数 L85/L90 构造, - # 早于 parse_args,值取自 L70-72 的原始命令行 strstr —— 与 --detection 同范式) - parser.add_argument("--detection-deep", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Deep detection: probe every allocation and enable the periodic full-table scan (implies --detection; extremely slow, diagnostics only)") - parser.add_argument("--show-args", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show all parsed arguments") - parser.add_argument("--repl", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Start the interactive REPL (M2 session variables + M3 cross-fragment functions: fragment -> DLL -> call)") - 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 mlirlite 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 = 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 - - # === --repl 守卫:与 --phase2command / --asan 互斥 === - # 位置:在下面 --asan / --phase2command 的全局状态设置与清理副作用之前 - #(--asan 会切 includes 缓存后缀并清空 .binary_asan、--phase2command 会设置 - # BuildPipeline._phase2_cmd_path),故必须最先判定。 - # ① --phase2command:_phase2_cmd_path 非空时 compile_ll_to_obj 只把 opt/llc - # 命令写进 bat 而**不执行**(BuildPipeline.vp:322-344)⇒ 片段 .obj 不会生成, - # Repl 第 ⑩ 步的 clang++ -shared 必然失败(Repl.vp 的 ReplExecFragment)。 - # ② --asan:会给片段 .ll 先做 opt -passes=asan 插桩 - # (BuildPipeline.vp:346-361),而片段产物是运行期 LoadLibraryA 载入的 DLL, - # 宿主进程并未挂 ASan 运行库,"片段 obj 被插桩"语义不明。 - repl_flag: bool = args.get_bool("repl") - if repl_flag: - if args.get_str("phase2command") is not None: - stdio.printf("FAIL: --repl 与 --phase2command 互斥\n") - stdio.printf(" 原因: --phase2command 下 compile_ll_to_obj 只写 bat 不执行 llc,\n") - stdio.printf(" 片段 .obj 不会生成,随后的 clang++ -shared 必然失败\n") - stdio.printf(" 处理: 去掉 --phase2command(REPL 需要立即产出并加载片段 DLL)\n") - argparse.release(args) - return 2 - if args.get_bool("asan"): - stdio.printf("FAIL: --repl 与 --asan 互斥\n") - stdio.printf(" 原因: --asan 先对片段 .ll 做 ASan 插桩,而片段产物是运行期\n") - stdio.printf(" LoadLibraryA 载入的 DLL,宿主进程未挂 ASan 运行库,插桩语义不明\n") - stdio.printf(" 处理: 去掉 --asan(如需排查内存问题,请用工程模式构建 ASan 版 TransPyV)\n") - argparse.release(args) - return 2 - # --traceback is also rejected under --repl: the fragment is a runtime-loaded - # DLL (DI metadata / startup ctor semantics undefined) and it has no .stackmap. - if args.get_bool("traceback"): - stdio.printf("FAIL: --repl 与 --traceback 互斥\n") - stdio.printf(" 原因: --traceback 依赖启动期构造器注入 + 本模块 DWARF 旁文件,\n") - stdio.printf(" 而片段是运行期 LoadLibraryA 载入的 DLL,二者语义未定义\n") - stdio.printf(" 处理: 去掉 --traceback(如需崩溃栈,请用工程模式构建 --traceback 版 TransPyV)\n") - argparse.release(args) - return 2 - - # === --repl: M1 交互式循环(表达式片段 → DLL → 调用回显)=== - # 分支位置:见下方"工程配置加载"之后(Repl 需要 Config 的 compiler/linker/target 通道), - # 仍在 Phase1/Phase2 之前;该分支直接返回。 - - # 打印解析结果 - # 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") - - # === --project 与 --src 互斥 === - # --project:传统 project.vpj 工程模式;--src:viperbuild 内建配置模式(入口文件)。 - # 两者的配置来源互斥,同时给无法确定以谁为准 ⇒ fail-fast。 - if project is not None and src is not None: - stdio.printf("FAIL: --project 与 --src 互斥\n") - stdio.printf(" 原因: --project 走 project.vpj 工程模式,--src 走入口文件的 viperbuild 内建配置\n") - stdio.printf(" 处理: 二选一;--src 模式下构建配置写在入口文件依赖闭包的 import viperbuild 里\n") - argparse.release(args) - return 2 - - _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") - - # 模式开关:includes 缓存用独立后缀目录隔离,避免不同模式产物的 obj 混链 - # (ASan 插桩 obj / traceback 帧指针+ctor obj 与非对应模式不兼容)。 - # --asan 与 --traceback 可同开 ⇒ 后缀必须一次性按两个开关组合计算, - # 后续 --clean/--rebuild-includes 经 Config.get_includes_binary_dir() 自动覆盖。 - gb_asan: bool = args.get_bool("asan") - gb_traceback: bool = args.get_bool("traceback") - if gb_asan: BuildPipeline._asan_mode = True - else: BuildPipeline._asan_mode = False - if gb_traceback: BuildPipeline._traceback_mode = True - else: BuildPipeline._traceback_mode = False - if gb_asan and gb_traceback: Config._inc_bin_suffix = ".binary_traceback_asan" - elif gb_traceback: Config._inc_bin_suffix = ".binary_traceback" - elif gb_asan: Config._inc_bin_suffix = ".binary_asan" - else: Config._inc_bin_suffix = ".binary" - if gb_asan: log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan") - if gb_traceback: log.info("Traceback 模式已启用(llc 保留帧指针 + 链 dbghelp + Install 构造器 + .stackmap)", "traceback") - # 开启任一模式都清空对应后缀的 includes 缓存,强制重建匹配当前模式的 obj - if gb_asan or gb_traceback: - mf_inc_bin_mode: str = Config.get_includes_binary_dir() - if mf_inc_bin_mode is not None: - n_mode: int = Utils.CleanDir(mf_inc_bin_mode) - log.info(f"模式缓存清理: {mf_inc_bin_mode} 删除 {n_mode} 个文件(将按 {Config._inc_bin_suffix} 重建)", "mode") - - # === --phase2command: 只生成命令写入 bat(Phase B/B+ 的 llc 与 Phase C 的 clang 链接命令),不执行 llc/链接 === - 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"): - mlirlite.G_PeepEnable = 0 - log.info("--no-llvmpeephole 已启用:关闭 FunctionPrint 本地窥孔(RenumberSSA 保留)", "peep") - - # === --repl 启动便利:未给 --project 时按 cwd → exe 同目录 查找 repl.vpj === - # 命中后按 --project 等价路径喂给下面既有的 Config.Load_project_config 流程 - #(不另写一套加载器);两处都没有则 fail-fast,诊断文本含"需要 --project"、 - # 两个已查位置与模板位置(由 Repl.ReplFindProject 写入 repl_diag_buf)。 - proj_loaded: int = 0 - proj_path: str = project - if repl_flag and proj_path is None: - repl_proj_buf: bytes = stdlib.malloc(REPL_PROJ_PATH_CAP) - repl_diag_buf: bytes = stdlib.malloc(REPL_PROJ_DIAG_CAP) - if repl_proj_buf is None or repl_diag_buf is None: - stdio.printf("FAIL: --repl 查找 repl.vpj 的路径缓冲分配失败\n") - argparse.release(args) - return 1 - if Repl.ReplFindProject(repl_proj_buf, REPL_PROJ_PATH_CAP, repl_diag_buf, REPL_PROJ_DIAG_CAP) != 0: - proj_path = repl_proj_buf - log.info(f"--repl: 未指定 --project,使用查找到的模板工程 {proj_path}", "repl") - else: - stdio.printf("FAIL: --repl 未找到工程文件 repl.vpj\n") - stdio.printf("%s", repl_diag_buf) - stdio.printf(" 也可用 --project 显式指定工程文件(如本仓库的 TransPyV/project.vpj)\n") - argparse.release(args) - return 1 - # 加载工程配置(显式 --project / --repl 查找到的 repl.vpj) - 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(当前目录和上级目录) - # --src 模式走 viperbuild 内建配置,不得再隐式加载 project.vpj(否则污染配置来源) - if src is None: - 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 解析 - if build_dir is not None: Config.BuildDir = build_dir - Config.resolve_paths(proj_dir) - Config.print_config() - - # === --src: viperbuild 内建配置发现与应用(Pass 0)=== - # 位置:project.vpj 加载之后、--asan/--clean/--phase 之前 —— 后续这些开关都要读 - # Config.TempDir/BuildDir/static_libs,必须先在 Pass 0 里从入口文件的依赖闭包 - # 提取 `import viperbuild` 配置并写回 Config。没找到 / 找到但坏一律 fail-fast。 - if src is not None: - log.banner("viperbuild") - if ViperBuild.DiscoverAndApply(mb, src) != 0: - argparse.release(args) - return 1 - Config.print_config() - - # === --repl: M1 交互式循环(表达式片段 → IR → llc → clang++ -shared → DLL → 调用回显)=== - # 位置:工程配置加载之后(Repl 经 Config 取 compiler/linker 命令与 target triple; - # 未加载 project.vpj 时 Repl 退回内置默认 llc/clang++),仍在 Phase1/Phase2 之前。 - if args.get_bool("repl"): - Repl._perm_pool = perm_pool # jmp_buf/解析表/会话缓冲须跨行存活,必须用 PermPool - if log is not None: - log.set_jit_flag(True) # error 改为 longjmp 回 REPL(Repl.run 退出时复位 False) - rc_repl: int = Repl.run(mb) - argparse.release(args) - return rc_repl - - # --asan 要求工程在 project.vpj 的 linker.static_libs 声明 ASan 运行库(TPV 不硬编码路径),缺失则 fail-fast - 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 参数控制: 1=仅 stub 分离;2=仅 stub 合并+编译;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 退出(否则未初始化的 Config 字段为垃圾指针 → 崩溃) - if proj_loaded == 0 and src_path is None: - log.error("未找到 project.vpj(当前目录/上级目录)且未指定 --src,无法构建", "config") - argparse.release(args) - return 1 - - # === 工程模式(多文件) === - # --project:project.vpj 工程模式(扫 source_dir 下所有 .vp 编译); - # --src + viperbuild:Pass 0 已把配置写回 Config,同样按工程模式构建(SourceDir 为根)。 - # 二者在参数解析处已判定互斥,不会同时成立。 - if Config.SourceDir is not None and (project is not None or ViperBuild.IsApplied() != 0): - 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 - - # 走到这里说明没有任何可构建的工程根(SourceDir 缺失)—— fail-fast,不再静默"完成" - log.error("无可构建的工程根:project.vpj 缺 source_dir,或 viperbuild 未提供 source", "config") - argparse.release(args) - return 1 +import lib.core.Gen.GenApi as GenApi +import lib.core.VLogger as VLogger +import lib.core.Repl as Repl +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.core.builtin.ViperBuild as ViperBuild +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 mlirlite +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 +define POOL_SIZE = 1073741824 +# Permanent memory pool size: 2^29 + 264 = 512MB usable, then change to 1 GiB +define PERM_POOL_SIZE = 1073741824 +# Code page: UTF-8 (Windows 65001) +define CODE_PAGE = 65001 +# Source code buffer size (1MB) +define SRC_BUF_SIZE = 1048576 +# Version (dev/beta/rc) +V_TPV_VERSION: __attribute__(section(".PRVERSN")) []char = "0.0.5-dev.479" +define V_VIPER_VERSION = "1.0.5-dev.970 2026-09-28 Non-LTS" +define V_RUNTIME_VERSION = "0.2.3+19-Non-LTS" +define V_TPC_COMMIT = "e376b949c9" +define V_TPV_COMMIT = "ab0f37e1a8" +define V_RUNTIME_COMMIT = "f087aa88e2" +define V_TARGET = "x86_64-w64-windows-gnu" +define V_LLVM = "22.1.8" +# --repl 模板工程查找缓冲(repl.vpj 命中路径 / 未命中诊断文本) +define REPL_PROJ_PATH_CAP = 1024 +define REPL_PROJ_DIAG_CAP = 2048 + +static a: int +# ============================================================ +# 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 + + # --detection-deep:重诊断(每次 alloc 探测 + 周期全表扫描,极慢,默认关);须在 MemBuddy 构造前用原始命令行 strstr 取值 + deep_detect_flag: int = 0 + if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection-deep") is not None: deep_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* = memhub.MemBuddy(arena, POOL_SIZE, detect_flag, deep_detect_flag) + if mb is None: + stdio.printf("FAIL: MemBuddy init failed\n") + return 1 + + perm_pool: memhub.MemBuddy* = memhub.MemBuddy(perm_arena, PERM_POOL_SIZE, detect_flag, deep_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: rawptr[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 阶段即调用其状态函数);模块级全局带 sha1 前缀(模块隔离),必须逐个模块赋值 + GenApi._g_gen = gen + # P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例 + + # 初始化 VLogger 并打印启动日志 + VLogger.init_logger() + log: VLogger.Logger = 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 = 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("--traceback", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Traceback mode: force-compile includes/traceback.vp + Install() ctor, llc -frame-pointer=all, link -ldbghelp, export .stackmap") + 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)") + # 仅注册以便 argparse 接受该标志(取值不读这里:MemBuddy 在本函数 L85/L90 构造, + # 早于 parse_args,值取自 L70-72 的原始命令行 strstr —— 与 --detection 同范式) + parser.add_argument("--detection-deep", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Deep detection: probe every allocation and enable the periodic full-table scan (implies --detection; extremely slow, diagnostics only)") + parser.add_argument("--show-args", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show all parsed arguments") + parser.add_argument("--repl", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Start the interactive REPL (M2 session variables + M3 cross-fragment functions: fragment -> DLL -> call)") + 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 mlirlite 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 = 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 + + # === --repl 守卫:与 --phase2command / --asan 互斥 === + # 位置:在下面 --asan / --phase2command 的全局状态设置与清理副作用之前 + #(--asan 会切 includes 缓存后缀并清空 .binary_asan、--phase2command 会设置 + # BuildPipeline._phase2_cmd_path),故必须最先判定。 + # ① --phase2command:_phase2_cmd_path 非空时 compile_ll_to_obj 只把 opt/llc + # 命令写进 bat 而**不执行**(BuildPipeline.vp:322-344)⇒ 片段 .obj 不会生成, + # Repl 第 ⑩ 步的 clang++ -shared 必然失败(Repl.vp 的 ReplExecFragment)。 + # ② --asan:会给片段 .ll 先做 opt -passes=asan 插桩 + # (BuildPipeline.vp:346-361),而片段产物是运行期 LoadLibraryA 载入的 DLL, + # 宿主进程并未挂 ASan 运行库,"片段 obj 被插桩"语义不明。 + repl_flag: bool = args.get_bool("repl") + if repl_flag: + if args.get_str("phase2command") is not None: + stdio.printf("FAIL: --repl 与 --phase2command 互斥\n") + stdio.printf(" 原因: --phase2command 下 compile_ll_to_obj 只写 bat 不执行 llc,\n") + stdio.printf(" 片段 .obj 不会生成,随后的 clang++ -shared 必然失败\n") + stdio.printf(" 处理: 去掉 --phase2command(REPL 需要立即产出并加载片段 DLL)\n") + argparse.release(args) + return 2 + if args.get_bool("asan"): + stdio.printf("FAIL: --repl 与 --asan 互斥\n") + stdio.printf(" 原因: --asan 先对片段 .ll 做 ASan 插桩,而片段产物是运行期\n") + stdio.printf(" LoadLibraryA 载入的 DLL,宿主进程未挂 ASan 运行库,插桩语义不明\n") + stdio.printf(" 处理: 去掉 --asan(如需排查内存问题,请用工程模式构建 ASan 版 TransPyV)\n") + argparse.release(args) + return 2 + # --traceback is also rejected under --repl: the fragment is a runtime-loaded + # DLL (DI metadata / startup ctor semantics undefined) and it has no .stackmap. + if args.get_bool("traceback"): + stdio.printf("FAIL: --repl 与 --traceback 互斥\n") + stdio.printf(" 原因: --traceback 依赖启动期构造器注入 + 本模块 DWARF 旁文件,\n") + stdio.printf(" 而片段是运行期 LoadLibraryA 载入的 DLL,二者语义未定义\n") + stdio.printf(" 处理: 去掉 --traceback(如需崩溃栈,请用工程模式构建 --traceback 版 TransPyV)\n") + argparse.release(args) + return 2 + + # === --repl: M1 交互式循环(表达式片段 → DLL → 调用回显)=== + # 分支位置:见下方"工程配置加载"之后(Repl 需要 Config 的 compiler/linker/target 通道), + # 仍在 Phase1/Phase2 之前;该分支直接返回。 + + # 打印解析结果 + # 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") + + # === --project 与 --src 互斥 === + # --project:传统 project.vpj 工程模式;--src:viperbuild 内建配置模式(入口文件)。 + # 两者的配置来源互斥,同时给无法确定以谁为准 ⇒ fail-fast。 + if project is not None and src is not None: + stdio.printf("FAIL: --project 与 --src 互斥\n") + stdio.printf(" 原因: --project 走 project.vpj 工程模式,--src 走入口文件的 viperbuild 内建配置\n") + stdio.printf(" 处理: 二选一;--src 模式下构建配置写在入口文件依赖闭包的 import viperbuild 里\n") + argparse.release(args) + return 2 + + _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") + + # 模式开关:includes 缓存用独立后缀目录隔离,避免不同模式产物的 obj 混链 + # (ASan 插桩 obj / traceback 帧指针+ctor obj 与非对应模式不兼容)。 + # --asan 与 --traceback 可同开 ⇒ 后缀必须一次性按两个开关组合计算, + # 后续 --clean/--rebuild-includes 经 Config.get_includes_binary_dir() 自动覆盖。 + gb_asan: bool = args.get_bool("asan") + gb_traceback: bool = args.get_bool("traceback") + if gb_asan: BuildPipeline._asan_mode = True + else: BuildPipeline._asan_mode = False + if gb_traceback: BuildPipeline._traceback_mode = True + else: BuildPipeline._traceback_mode = False + if gb_asan and gb_traceback: Config._inc_bin_suffix = ".binary_traceback_asan" + elif gb_traceback: Config._inc_bin_suffix = ".binary_traceback" + elif gb_asan: Config._inc_bin_suffix = ".binary_asan" + else: Config._inc_bin_suffix = ".binary" + if gb_asan: log.info("ASan 模式已启用(opt -passes=asan 插桩; 运行库需 project.vpj 声明)", "asan") + if gb_traceback: log.info("Traceback 模式已启用(llc 保留帧指针 + 链 dbghelp + Install 构造器 + .stackmap)", "traceback") + # 开启任一模式都清空对应后缀的 includes 缓存,强制重建匹配当前模式的 obj + if gb_asan or gb_traceback: + mf_inc_bin_mode: str = Config.get_includes_binary_dir() + if mf_inc_bin_mode is not None: + n_mode: int = Utils.CleanDir(mf_inc_bin_mode) + log.info(f"模式缓存清理: {mf_inc_bin_mode} 删除 {n_mode} 个文件(将按 {Config._inc_bin_suffix} 重建)", "mode") + + # === --phase2command: 只生成命令写入 bat(Phase B/B+ 的 llc 与 Phase C 的 clang 链接命令),不执行 llc/链接 === + 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"): + mlirlite.G_PeepEnable = 0 + log.info("--no-llvmpeephole 已启用:关闭 FunctionPrint 本地窥孔(RenumberSSA 保留)", "peep") + + # === --repl 启动便利:未给 --project 时按 cwd → exe 同目录 查找 repl.vpj === + # 命中后按 --project 等价路径喂给下面既有的 Config.Load_project_config 流程 + #(不另写一套加载器);两处都没有则 fail-fast,诊断文本含"需要 --project"、 + # 两个已查位置与模板位置(由 Repl.ReplFindProject 写入 repl_diag_buf)。 + proj_loaded: int = 0 + proj_path: str = project + if repl_flag and proj_path is None: + repl_proj_buf: bytes = stdlib.malloc(REPL_PROJ_PATH_CAP) + repl_diag_buf: bytes = stdlib.malloc(REPL_PROJ_DIAG_CAP) + if repl_proj_buf is None or repl_diag_buf is None: + stdio.printf("FAIL: --repl 查找 repl.vpj 的路径缓冲分配失败\n") + argparse.release(args) + return 1 + if Repl.ReplFindProject(repl_proj_buf, REPL_PROJ_PATH_CAP, repl_diag_buf, REPL_PROJ_DIAG_CAP) != 0: + proj_path = repl_proj_buf + log.info(f"--repl: 未指定 --project,使用查找到的模板工程 {proj_path}", "repl") + else: + stdio.printf("FAIL: --repl 未找到工程文件 repl.vpj\n") + stdio.printf("%s", repl_diag_buf) + stdio.printf(" 也可用 --project 显式指定工程文件(如本仓库的 TransPyV/project.vpj)\n") + argparse.release(args) + return 1 + # 加载工程配置(显式 --project / --repl 查找到的 repl.vpj) + 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(当前目录和上级目录) + # --src 模式走 viperbuild 内建配置,不得再隐式加载 project.vpj(否则污染配置来源) + if src is None: + 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 解析 + if build_dir is not None: Config.BuildDir = build_dir + Config.resolve_paths(proj_dir) + Config.print_config() + + # === --src: viperbuild 内建配置发现与应用(Pass 0)=== + # 位置:project.vpj 加载之后、--asan/--clean/--phase 之前 —— 后续这些开关都要读 + # Config.TempDir/BuildDir/static_libs,必须先在 Pass 0 里从入口文件的依赖闭包 + # 提取 `import viperbuild` 配置并写回 Config。没找到 / 找到但坏一律 fail-fast。 + if src is not None: + log.banner("viperbuild") + if ViperBuild.DiscoverAndApply(mb, src) != 0: + argparse.release(args) + return 1 + Config.print_config() + + # === --repl: M1 交互式循环(表达式片段 → IR → llc → clang++ -shared → DLL → 调用回显)=== + # 位置:工程配置加载之后(Repl 经 Config 取 compiler/linker 命令与 target triple; + # 未加载 project.vpj 时 Repl 退回内置默认 llc/clang++),仍在 Phase1/Phase2 之前。 + if args.get_bool("repl"): + Repl._perm_pool = perm_pool # jmp_buf/解析表/会话缓冲须跨行存活,必须用 PermPool + if log is not None: + log.set_jit_flag(True) # error 改为 longjmp 回 REPL(Repl.run 退出时复位 False) + rc_repl: int = Repl.run(mb) + argparse.release(args) + return rc_repl + + # --asan 要求工程在 project.vpj 的 linker.static_libs 声明 ASan 运行库(TPV 不硬编码路径),缺失则 fail-fast + 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 参数控制: 1=仅 stub 分离;2=仅 stub 合并+编译;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 退出(否则未初始化的 Config 字段为垃圾指针 → 崩溃) + if proj_loaded == 0 and src_path is None: + log.error("未找到 project.vpj(当前目录/上级目录)且未指定 --src,无法构建", "config") + argparse.release(args) + return 1 + + # === 工程模式(多文件) === + # --project:project.vpj 工程模式(扫 source_dir 下所有 .vp 编译); + # --src + viperbuild:Pass 0 已把配置写回 Config,同样按工程模式构建(SourceDir 为根)。 + # 二者在参数解析处已判定互斥,不会同时成立。 + if Config.SourceDir is not None and (project is not None or ViperBuild.IsApplied() != 0): + 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" + 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 + + # 走到这里说明没有任何可构建的工程根(SourceDir 缺失)—— fail-fast,不再静默"完成" + log.error("无可构建的工程根:project.vpj 缺 source_dir,或 viperbuild 未提供 source", "config") + argparse.release(args) + return 1 diff --git a/repl.vpj b/repl.vpj index 702f4e1..4a0a787 100644 --- a/repl.vpj +++ b/repl.vpj @@ -20,7 +20,7 @@ "-lpthread", "-lmingwex", "-lkernel32", - "-Wl,--allow-multiple-definition", + "", "-Wl,--stack,0x1000000", "-Wl,--no-insert-timestamp", "-g"