新增 _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,非本次会话产出)
711 lines
39 KiB
Plaintext
711 lines
39 KiB
Plaintext
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
|