Files
ViperC/App/main.vp
T
TermiNexus e2c4c7645e fix(cast): 补继承上转豁免(Derived*→Base* 放行),与 TPC 同口径
新增 _class_name_of_ptr_pointee(复用 StructGen.get_class_name_by_type)与 _is_inheritance_upcast(复用 StructGen.get_mro_chain:目标类名命中源类 MRO 链自身之后的祖先即放行,含多级)

在 _pointee_convert_reason 落入“非基本类型”分支前插入上转豁免;其余规则(通用指针通配/层数不同即禁/完全相同放行/None 任意/构造物化豁免/标量无损)未动;Base*→Derived* 与无关结构体仍禁

注:本提交顺带带入 App/main.vp 的既有版本号改动(0.0.5-dev.479,非本次会话产出)
2026-09-28 15:29:42 +08:00

711 lines
39 KiB
Plaintext
Raw Permalink 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
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
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 llvmlite peephole passes (T2 store/load forward & ret-zero, T3 no-op bitcast) in FunctionPrint serialization")
parser.add_argument("--version", "-v", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show version and exit")
parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show help and exit")
# 解析命令行参数
args: argparse.ParsedArgs = 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")
_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"):
llvmlite.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(当前目录和上级目录)
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()
# === --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 指定但 --src 未指定时,扫描 source_dir 下所有 .vp/.py 文件编译
if src_path is None and project is not None and Config.SourceDir is not None:
mf_temp: str = Config.TempDir if Config.TempDir is not None else "."
mf_output: str = Config.OutputDir if Config.OutputDir is not None else "."
mf_cc: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
mf_cc_flags: str = Config.CompilerFlags if Config.CompilerFlags is not None else "-filetype=obj -relocation-model=pic"
mf_linker: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
mf_linker_flags: str = Config.LinkerFlags if Config.LinkerFlags is not None else "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition"
mf_linker_out: str = Config.LinkerOutput if Config.LinkerOutput is not None else "app.exe"
mf_static_libs: str = Config.LinkerStaticLibs
mf_dynamic_libs: str = Config.LinkerDynamicLibs
mf_includes_bin: str = Config.get_includes_binary_dir()
mf_ret: int = Phase2.RunMultiFileProject(
mb, Config.SourceDir, mf_temp, mf_output,
mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out,
mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args,
mf_static_libs, mf_dynamic_libs)
argparse.release(args)
log.success("TransPyV 完成")
return mf_ret
# === 单文件模式:--src 指定时使用指定文件;--project 但无 SourceDir 时回退 ===
if src_path is None and project is not None:
if Config.SourceDir is not None:
# 构造入口路径: SourceDir/main.vp(不再做 vp -> py 后缀识别回退)
sd_len: sizet = string.strlen(Config.SourceDir)
path_buf: bytes = stdlib.malloc(sd_len + 16)
if path_buf is not None:
viperlib.snprintf(path_buf, sd_len + 16, "%s/main.vp", Config.SourceDir)
src_path = path_buf
log.info(f"入口文件: {src_path}", "project")
if src_path is not None:
log.banner("AST 解析")
# 打开文件
f: fileio.File = fileio.File(src_path, fileio.MODE.R)
if f.closed:
log.error(f"无法打开文件: {src_path}", "fileio")
argparse.release(args)
return 1
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被**静默截断**;
# 这里先按文件大小拒绝, 否则拿截断后的源码去 parse 只会得到莫名其妙的语法错误
if f.size() >= SRC_BUF_SIZE:
log.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {src_path}", "fileio")
f.close()
argparse.release(args)
return 1
# 分配源代码缓冲区
src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE)
if src_buf is None:
log.error("malloc for src_buf failed", "memhub")
f.close()
argparse.release(args)
return 1
# 读取文件内容
bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE)
f.close()
if bytes_read < 0:
log.error(f"读取文件失败 (错误码: {bytes_read})", "fileio")
argparse.release(args)
return 1
# 添加 null 终止符,确保 strlen 和 SHA1 计算正确
if bytes_read < SRC_BUF_SIZE:
src_buf[bytes_read] = 0
# 解析 AST(拆分为词法+语法两阶段,便于调试)
ast._init_tables(mb)
lx: ast.Lexer* = ast.new_lexer(mb)
ast._lexer_init(lx, src_buf, mb)
tokens: ast.Token* = ast.tokenize(lx)
tree: ast.AST* = ast.parse_tokens(mb, tokens)
if tree is None:
# 解析失败:行/列 + 原因一并进日志行(原因由 parser.vp 的 _record_* /
# parse_tokens 兜底补记提供)
log.error(f"AST 解析失败(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: {ast.get_parse_err_msg()})", "ast")
argparse.release(args)
return 1
# === 翻译 AST → LLVM IR ===
log.banner("LLVM IR 翻译")
tr: rawptr[HandlesTranslator.Translator] = HandlesTranslator.Translator()
result: int = tr.translate(tree)
if result == 0:
IR_BUF_SIZE: sizet = 262144
ir_buf: bytes = stdlib.malloc(IR_BUF_SIZE)
if ir_buf is not None:
tr.dump_ir(ir_buf, IR_BUF_SIZE, llvmlite.OUTPUT_FULL)
# === 编译管线: .ll → .obj → .exe ===
log.banner("编译管线")
# module_name 用源代码内容的 SHA1(前 16 字符),与 Projectrans.py 一致
# output_name 从文件名推导(如 test.vp -> test.exe),保持可读性
module_name: str = Utils.compute_sha1(mb, src_buf)
if module_name is None:
VLogger.error("SHA1 计算失败", "main")
output_name: str = "test_prog.exe"
# 从文件名提取 output_name(去掉路径和 .vp/.py 扩展名)
src_len: sizet = string.strlen(src_path)
if src_len > 0:
# 找到最后一个 / 或 \ 之后的部分
base_start: sizet = src_len
i: sizet = src_len
while i > 0:
i -= 1
ch: char = src_path[i]
if ch == '/' or ch == '\\':
base_start = i + 1
break
if i == 0:
base_start = 0
# 找到 .vp/.py 扩展名
base_end: sizet = src_len
j: sizet = base_start
while j < src_len:
if src_path[j] == '.':
base_end = j
break
j += 1
# 输出名 = 文件名 + ".exe"
name_len: sizet = base_end - base_start
if name_len > 0:
out_len: sizet = name_len + 5
output_name = stdlib.malloc(out_len)
if output_name is not None:
# 手动复制文件名并追加 .exe(viperlib.snprintf 不支持 %.*s,string 无 strcat)
string.strncpy(output_name, src_path + base_start, name_len)
output_name[name_len] = '.'
output_name[name_len + 1] = 'e'
output_name[name_len + 2] = 'x'
output_name[name_len + 3] = 'e'
output_name[name_len + 4] = '\0'
# 使用 project.vpj 中的配置
temp_dir: str = Config.TempDir if Config.TempDir is not None else "."
output_dir: str = Config.OutputDir if Config.OutputDir is not None else "."
# 设置全局 temp_dir(供跨模块 CDefine 查找使用)
GenApi.set_temp_dir(temp_dir)
cc_cmd: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
cc_flags: str = "-filetype=obj -relocation-model=pic"
linker_cmd: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
linker_flags: str = "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition"
linker_static_libs: str = Config.LinkerStaticLibs
linker_dynamic_libs: str = Config.LinkerDynamicLibs
# --project 模式且未显式指定 --src 时:使用 config 中的 linker_output(如 TransPyV.exe)
# 显式指定 --src 时:使用从文件名推导的 output_name(如 test.exe),避免覆盖正在运行的 exe
if project is not None and Config.LinkerOutput is not None and args.get_str("src") is None:
linker_output: str = Config.LinkerOutput
else:
linker_output: str = output_name
# 计算 includes.binary 目录路径(链接时附加预编译 .obj)
includes_binary_dir: str = Config.get_includes_binary_dir()
ir_len: sizet = string.strlen(ir_buf)
# (--phase 参数已在配置加载后处理,此处直接使用 do_phase1/do_phase2)
# === Phase1 stub 分离:使用 OUTPUT_STUB/TEXT 模式分别生成 stub.ll 和 text.ll ===
if do_phase1 != 0 and temp_dir is not None and module_name is not None:
log.banner("stub 分离")
SF_IR_SIZE: sizet = 262144
td_len_sf: sizet = string.strlen(temp_dir)
# 保存 stub.ll
sf_stub_buf: bytes = stdlib.malloc(SF_IR_SIZE)
if sf_stub_buf is not None:
tr.dump_ir(sf_stub_buf, SF_IR_SIZE, llvmlite.OUTPUT_STUB)
sf_stub_len: sizet = string.strlen(sf_stub_buf)
sf_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "stub.ll")
if sf_stub_path is not None:
sf_f: rawptr[fileio.File] = fileio.File(sf_stub_path, fileio.MODE.W)
if not sf_f.closed:
sf_f.write(sf_stub_buf, sf_stub_len)
sf_f.close()
stdlib.free(sf_stub_path)
stdlib.free(sf_stub_buf)
# 保存 text.ll
sf_text_buf: bytes = stdlib.malloc(SF_IR_SIZE)
if sf_text_buf is not None:
tr.dump_ir(sf_text_buf, SF_IR_SIZE, llvmlite.OUTPUT_TEXT)
sf_text_len: sizet = string.strlen(sf_text_buf)
sf_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "text.ll")
if sf_text_path is not None:
sf_tf: rawptr[fileio.File] = fileio.File(sf_text_path, fileio.MODE.W)
if not sf_tf.closed:
sf_tf.write(sf_text_buf, sf_text_len)
sf_tf.close()
stdlib.free(sf_text_path)
stdlib.free(sf_text_buf)
# phase=1 模式:仅生成 stub,不执行编译
if do_phase2 == 0:
log.success("Phase1 完成(仅 stub 分离,跳过编译)")
argparse.release(args)
return 0
# === Phase2: 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR ===
final_ir: bytes = ir_buf
final_ir_len: sizet = ir_len
if temp_dir is not None and module_name is not None:
log.banner("IR 组合")
SF_COMBINED_SIZE: sizet = 4194304 # 4MB
combined_buf: bytes = stdlib.malloc(SF_COMBINED_SIZE)
if combined_buf is not None:
combined_len: sizet = StubMergerBuild.BuildCombinedIR(temp_dir, module_name, combined_buf, SF_COMBINED_SIZE)
if combined_len > 0:
final_ir = combined_buf
final_ir_len = combined_len
else:
stdlib.free(combined_buf)
VLogger.error("BuildCombinedIR 失败,终止编译", "stub")
br: rawptr[BuildPipeline.BuildResult] = BuildPipeline.run_pipeline(
final_ir, final_ir_len, temp_dir, output_dir, module_name,
cc_cmd, cc_flags, linker_cmd, linker_flags, linker_output,
includes_binary_dir, linker_static_libs, linker_dynamic_libs
)
if br is not None and br.Success == 1:
log.success("编译管线完成")
log.info(f"输出: {output_dir}/{linker_output}")
# --run 模式:执行生成的可执行文件
# phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run
if args.get_bool("run") and BuildPipeline._phase2_cmd_path is None:
exe_path: bytes = stdlib.malloc(string.strlen(output_dir) + string.strlen(linker_output) + 2)
if exe_path is not None:
viperlib.snprintf(exe_path, 256, "%s/%s", output_dir, linker_output)
log.info(f"执行: {exe_path}")
r: rawptr[subprocess.CompletedProcess] = subprocess.run(exe_path, False, False)
if r is not None:
log.info(f"退出码: {r.returncode}")
else:
log.error("编译管线失败", "pipeline")
if br is not None and br.ErrorMsg is not None:
log.error(f"编译管线失败: {br.ErrorMsg}", "FATAL")
sys.exit(1)
else:
log.error("malloc for IR buffer failed", "memhub")
else:
log.error("翻译失败", "translator")
argparse.release(args)
log.success("TransPyV 完成")
return 0