Files
ViperC/App/main.vp
T
TermiNexus a2874a14e6 chore(tpv): 移除 --allow-multiple-definition 兜底并同步缺省链接参数
main.vp 的缺省 linker flags 与 ViperBuild 内置 _c_linker 默认值去掉 -Wl,--allow-multiple-definition,与 project.vpj 同口径;Phase2 三处引用该 flag 的注释改为描述链接器选 main / 链接前按路径去重的实际机制。重建验证:跨模块内联优化完成、链接成功 139.17s。
2026-10-05 09:22:58 +08:00

494 lines
28 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenApi as GenApi
import 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 <exe>.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 <https://opensource.org/licenses/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 <exe>.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 构造器 + <exe>.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