690 lines
35 KiB
Plaintext
690 lines
35 KiB
Plaintext
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Handles.HandlesTranslator as HandlesTranslator
|
||
import lib.core.Handles.HandlesExpr as HandlesExpr
|
||
import lib.core.Handles.HandlesExprCall as HandlesExprCall
|
||
import lib.Phase.BuildPipeline as BuildPipeline
|
||
import lib.core.IncludesScanner as IncludesScanner
|
||
import lib.Stub.StubMerger as StubMerger
|
||
import lib.Phase.Phase1 as Phase1
|
||
import lib.Phase.Phase2 as Phase2
|
||
import lib.core.CompilerFactory as CompilerFactory
|
||
import lib.core.Gen.Generator as GenGenerator
|
||
import lib.Projectrans.Config as Config
|
||
import lib.Projectrans.Utils as Utils
|
||
import lib
|
||
from stdint import *
|
||
import stdio
|
||
import string
|
||
import stdlib
|
||
import memhub
|
||
import argparse
|
||
import w32.win32console as w32cmd
|
||
import w32.win32base as w32base
|
||
import w32.fileio as fileio
|
||
import sys
|
||
import ast
|
||
import llvmlite
|
||
import subprocess
|
||
import viperlib
|
||
import t, c
|
||
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
|
||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||
import lib.Phase.Phase1.Phase1Main as Phase1Main
|
||
|
||
|
||
# Main Pool 1 GiB
|
||
POOL_SIZE: t.CDefine = 1073741824
|
||
# Permanent memory pool size: 2^29 + 264 = 512MB usable
|
||
PERM_POOL_SIZE: t.CDefine = 536871176
|
||
# Code page: UTF-8 (Windows 65001)
|
||
CODE_PAGE: t.CDefine = 65001
|
||
# Source code buffer size (1MB)
|
||
SRC_BUF_SIZE: t.CDefine = 1048576
|
||
# Version (dev/beta/rc)
|
||
V_VIPER_VERSION: t.CDefine = "1.0.3-dev.30 2026-07-27 Non-LTS"
|
||
V_TPV_VERSION: t.CDefine = "0.0.3-dev.230"
|
||
V_RUNTIME_VERSION: t.CDefine = "0.2.2+4-Non-LTS"
|
||
V_TPC_COMMIT: t.CDefine = "1dc23ffdde"
|
||
V_TPV_COMMIT: t.CDefine = "f4fee79d4d"
|
||
V_RUNTIME_COMMIT: t.CDefine = "4ce6095359"
|
||
V_TARGET: t.CDefine = "x86_64-w64-windows-gnu"
|
||
V_LLVM: t.CDefine = "22.1.8"
|
||
|
||
# ============================================================
|
||
# main: Command-line entry
|
||
# ============================================================
|
||
def main() -> int:
|
||
w32cmd.SetConsoleOutputCP(CODE_PAGE)
|
||
w32cmd.SetConsoleCP(CODE_PAGE)
|
||
|
||
# [DET] 分配检测开关:在创建内存池前扫描命令行(sys._init_argv 依赖 mb,
|
||
# 不能提前;GetCommandLineA 直接读原始命令行,不含任何 pool 分配)。
|
||
# --detection 开启后 mb/perm_pool 均启用前后 0xCDCD 标记 + 记录表探测
|
||
# 越界写(定位"存活块被部分覆盖"的写入者,如 KwEntry cargs)。
|
||
detect_flag: int = 0
|
||
cmd_line_d: str = w32base.GetCommandLineA()
|
||
if cmd_line_d is not None and string.strstr(cmd_line_d, "--detection") is not None:
|
||
detect_flag = 1
|
||
|
||
# Initialize the mbuddy memory pool
|
||
arena: bytes = stdlib.malloc(POOL_SIZE)
|
||
if arena is None:
|
||
stdio.printf("FAIL: malloc for arena failed\n")
|
||
return 1
|
||
# Create a global permanent memory pool (perm_pool, 512MB)
|
||
perm_arena: bytes = stdlib.malloc(PERM_POOL_SIZE)
|
||
if perm_arena is None:
|
||
stdio.printf("FAIL: malloc for perm_pool arena failed\n")
|
||
return 1
|
||
|
||
mb: memhub.MemBuddy | t.CPtr = memhub.MemBuddy(arena, POOL_SIZE, detect_flag)
|
||
if mb is None:
|
||
stdio.printf("FAIL: MemBuddy init failed\n")
|
||
return 1
|
||
|
||
perm_pool: memhub.MemBuddy | t.CPtr = memhub.MemBuddy(perm_arena, PERM_POOL_SIZE, detect_flag)
|
||
if perm_pool is None:
|
||
stdio.printf("FAIL: perm_pool init failed\n")
|
||
return 1
|
||
|
||
CompilerFactory.set_factory(CompilerFactory.CompilerFactory(mb, perm_pool))
|
||
|
||
# 设置全局 mbuddy 指针(sys 和 argparse 都需要)
|
||
sys._mbuddy = mb
|
||
argparse._mbuddy = mb
|
||
ast._mbuddy = mb
|
||
lib._mbuddy = mb
|
||
Config._mbuddy = mb
|
||
Utils._mbuddy = mb
|
||
HandlesTranslator._mbuddy = mb
|
||
BuildPipeline._mbuddy = mb
|
||
IncludesScanner._mbuddy = mb
|
||
StubMerger._mbuddy = mb
|
||
Phase1._mbuddy = mb
|
||
Phase2._mbuddy = mb
|
||
subprocess._mbuddy = mb
|
||
# hashlib._mbuddy = (mb)
|
||
VLogger._mbuddy = mb
|
||
lib._mbuddy = mb
|
||
|
||
# P2: 创建项目级 Generator 单例(Phase1/Phase2/BuildPipeline 全程复用;
|
||
gen: GenGenerator.Generator | t.CPtr = mb.alloc(GenGenerator.Generator.__sizeof__())
|
||
if gen is None:
|
||
stdio.printf("FAIL: Generator alloc failed\n")
|
||
return 1
|
||
gen.__before_init__()
|
||
gen.__init__(mb)
|
||
|
||
GenGenerator._g_gen = gen
|
||
# P2-P5: 注入各 Handles 模块级单例(Phase1 阶段即调用其状态函数)。
|
||
# 注意:TPV 编译模块级全局带 sha1 前缀(模块隔离),必须逐个模块赋值;
|
||
# 种子 TPC 时代用裸名 @_g_gen(全局共享),遗漏三个 Handles 模块无感,
|
||
# 自举后 HT/HE/HEC 委托读各自 _g_gen=None 恒失效(is_global_name 恒 0 误报)
|
||
GenApi._g_gen = gen
|
||
HandlesTranslator._g_gen = gen
|
||
HandlesExpr._g_gen = gen
|
||
HandlesExprCall._g_gen = gen
|
||
# P6: 注入语句/赋值/变量/导入/翻译驱动模块的单例
|
||
|
||
# 初始化 VLogger 并打印启动日志
|
||
log: VLogger.Logger | t.CPtr = VLogger.get_logger()
|
||
if log is None:
|
||
log.error("TransPyV Failed to parse parameters")
|
||
|
||
# 初始化命令行参数(Windows: GetCommandLineA, POSIX: /proc/self/cmdline)
|
||
sys._init_argv()
|
||
|
||
# 创建参数解析器
|
||
parser: argparse.ArgumentParser | t.CPtr = argparse.ArgumentParser("TransPyV", "TransPyV command-line parameter parser", pool=mb)
|
||
|
||
# 注册参数(与 Projectrans.py main() 一致)
|
||
parser.add_argument("--project", None, argparse.STRING, 0, None, False, argparse.STORE, "Project VPJ path, by default it looks in the current directory")
|
||
parser.add_argument("--src", None, argparse.STRING, 0, None, False, argparse.STORE, "Source directory")
|
||
parser.add_argument("--temp", None, argparse.STRING, 0, None, False, argparse.STORE, "Declare a temporary directory for the interface")
|
||
parser.add_argument("--output", None, argparse.STRING, 0, None, False, argparse.STORE, "Output directory")
|
||
parser.add_argument("--phase", None, argparse.STRING, 0, None, False, argparse.STORE, "Phase: 1 = Generate statement, 2 = Translate and compile, all = Everything")
|
||
parser.add_argument("--cc", None, argparse.STRING, 0, None, False, argparse.STORE, "LLVM compiler command")
|
||
parser.add_argument("--clean", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clean output and temp directories")
|
||
parser.add_argument("--run", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Run the generated executable after compilation")
|
||
parser.add_argument("--rebuild-includes", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Delete the includes.binary precompiled cache and rebuild all the includes")
|
||
parser.add_argument("--clear-cache", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Clear .transpyc_cache global cache")
|
||
parser.add_argument("--asan", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "ASan mode: .ll first opt -passes=asan then link -fsanitize=address")
|
||
parser.add_argument("--phase2command", None, argparse.STRING, 0, None, False, argparse.STORE, "Do not run llc/link: write Phase2 llc(opt+llc) and clang link commands to the given .bat path")
|
||
parser.add_argument("--nowarn-register", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Suppress Phase A dependency WARN")
|
||
parser.add_argument("--detection", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Enable MemBuddy allocation detection (0xCDCD markers + record-table probe for out-of-bounds writes; applies to both mb and perm_pool)")
|
||
parser.add_argument("--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("--version", "-v", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show version and exit")
|
||
parser.add_argument("--help", "-h", argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Show help and exit")
|
||
|
||
# 解析命令行参数
|
||
args: argparse.ParsedArgs | t.CPtr = parser.parse_args(sys._argc, sys._argv)
|
||
if args is None:
|
||
if log is not None:
|
||
log.error("参数解析失败", "argparse")
|
||
return 1
|
||
|
||
if args.get_bool("help"):
|
||
parser.print_help()
|
||
return 0
|
||
|
||
if args.get_bool("version"):
|
||
stdio.printf("""Viper %s
|
||
Copyright (C) 2026 Viper Project
|
||
License MIT <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
|
||
|
||
# 打印解析结果
|
||
#if log is not None:
|
||
# log.banner("TransPyV 参数解析结果")
|
||
|
||
# 字符串参数
|
||
project: str = args.get_str("project")
|
||
src: str = args.get_str("src")
|
||
temp: str = args.get_str("temp")
|
||
output: str = args.get_str("output")
|
||
phase: str = args.get_str("phase")
|
||
cc: str = args.get_str("cc")
|
||
build_dir: str = args.get_str("build-dir")
|
||
_gb_clean: INT = args.get_bool("clean")
|
||
#if project is not None: stdio.printf(" --project: %s\n", project)
|
||
#else: stdio.printf(" --project: (未指定)\n")
|
||
#if src is not None: stdio.printf(" --src: %s\n", src)
|
||
#else: stdio.printf(" --src: (未指定)\n")
|
||
#if temp is not None: stdio.printf(" --temp: %s\n", temp)
|
||
#else: stdio.printf(" --temp: (未指定)\n")
|
||
#if output is not None: stdio.printf(" --output: %s\n", output)
|
||
#else: stdio.printf(" --output: (未指定)\n")
|
||
#if phase is not None: stdio.printf(" --phase: %s\n", phase)
|
||
#else: stdio.printf(" --phase: (未指定,默认 all)\n")
|
||
#if cc is not None: stdio.printf(" --cc: %s\n", cc)
|
||
#else: stdio.printf(" --cc: (未指定)\n")
|
||
#if _gb_clean: stdio.printf(" --clean: True\n")
|
||
#else: stdio.printf(" --clean: False\n")
|
||
#if args.get_bool("run"): stdio.printf(" --run: True\n")
|
||
#else: stdio.printf(" --run: False\n")
|
||
#if args.get_bool("rebuild-includes"): stdio.printf(" --rebuild-includes: True\n")
|
||
#else: stdio.printf(" --rebuild-includes: False\n")
|
||
#if args.get_bool("clear-cache"): stdio.printf(" --clear-cache: True\n")
|
||
#else: stdio.printf(" --clear-cache: False\n")
|
||
|
||
#stdio.printf("\n参数解析完成。\n")
|
||
|
||
# ASan 模式
|
||
# includes 缓存用独立目录 .binary_asan 隔离 asan 插桩 obj,避免与非 asan obj 混链
|
||
# (asan obj 引用 __asan_init 等符号,非 asan 链接不提供 asan 库 → undefined reference)
|
||
if args.get_bool("asan"):
|
||
BuildPipeline._asan_mode = True
|
||
Config._inc_bin_suffix = ".binary_asan"
|
||
if log is not None:
|
||
log.info("ASan 模式已启用(opt -passes=asan + libclang_rt)", "asan")
|
||
# ASan 模式清理 asan includes 缓存(强制重建 asan 插桩 obj)
|
||
mf_inc_bin_asan: str = Config.get_includes_binary_dir()
|
||
if mf_inc_bin_asan is not None:
|
||
n_asan: int = Utils.CleanDir(mf_inc_bin_asan)
|
||
if log is not None:
|
||
fb_asan: str = VLogger.fmt_buf()
|
||
if fb_asan is not None:
|
||
viperlib.snprintf(fb_asan, 1024, "ASan 模式清理 includes 缓存: %d 个文件(将重建 asan obj)", n_asan)
|
||
log.info(fb_asan, "asan")
|
||
else:
|
||
BuildPipeline._asan_mode = False
|
||
Config._inc_bin_suffix = ".binary"
|
||
|
||
# === --phase2command: 只生成命令写入 bat,不执行 llc/链接 ===
|
||
# Phase B/B+ 的 llc(含 ASan opt)与 Phase C 的 clang 链接命令写入指定
|
||
# .bat 路径(BuildPipeline._phase2_write_cmd 首次写入 header,后续追加)。
|
||
p2c: str = args.get_str("phase2command")
|
||
if p2c is not None:
|
||
BuildPipeline._phase2_cmd_path = p2c
|
||
if log is not None:
|
||
fb_p2c: str = VLogger.fmt_buf()
|
||
if fb_p2c is not None:
|
||
viperlib.snprintf(fb_p2c, 1024, "--phase2command: 不执行 llc/链接,命令写入 %s", p2c)
|
||
log.info(fb_p2c, "phase2cmd")
|
||
|
||
# --nowarn-register:抑制"外部函数返回类型未登记"类 WARN 输出
|
||
if args.get_bool("nowarn-register"):
|
||
VLogger.set_nowarn_register(1)
|
||
if log is not None:
|
||
log.info("--nowarn-register 已启用:抑制注册类 WARN 输出", "vlogger")
|
||
|
||
# 加载 project.vpj 配置
|
||
proj_loaded: int = 0
|
||
proj_path: str = project
|
||
if proj_path is not None:
|
||
if log is not None:
|
||
log.banner("工程配置")
|
||
cfg_ret: int = Config.Load_project_config(proj_path)
|
||
if cfg_ret == 0:
|
||
proj_loaded = 1
|
||
else:
|
||
if log is not None:
|
||
warn_buf: str = stdlib.malloc(256)
|
||
if warn_buf is not None:
|
||
viperlib.snprintf(warn_buf, 256, "无法加载 project.vpj: %s", proj_path)
|
||
log.warning(warn_buf, "config")
|
||
stdlib.free(warn_buf)
|
||
else:
|
||
# 尝试默认路径 project.vpj(当前目录和上级目录)
|
||
proj_path = "project.vpj"
|
||
if Config.Load_project_config(proj_path) == 0:
|
||
proj_loaded = 1
|
||
else:
|
||
proj_path = "../project.vpj"
|
||
if Config.Load_project_config(proj_path) == 0:
|
||
proj_loaded = 1
|
||
if proj_loaded == 1:
|
||
# 提取 project.vpj 所在目录,将相对路径解析为基于该目录的路径
|
||
proj_len: t.CSizeT = string.strlen(proj_path)
|
||
slash_pos: t.CSizeT = proj_len
|
||
for i in range(proj_len):
|
||
idx: t.CSizeT = proj_len - 1 - i
|
||
ch: t.CChar = proj_path[idx]
|
||
if ch == '/' or ch == '\\':
|
||
slash_pos = idx
|
||
break
|
||
proj_dir: str = ""
|
||
if slash_pos > 0 and slash_pos < proj_len:
|
||
proj_dir = stdlib.malloc(slash_pos + 1)
|
||
if proj_dir is not None:
|
||
string.memcpy(proj_dir, proj_path, slash_pos)
|
||
proj_dir[slash_pos] = '\0'
|
||
# [--build-dir] 命令行覆盖 project.vpj 的 build_dir:在 resolve_paths 前
|
||
# 设置,使相对路径仍基于 project_dir 解析(如 ./tpv_build2 → Test/tpv_build2),
|
||
# 绝对路径(/ 或 \ 开头)由 _join_path 原样保留。
|
||
if build_dir is not None:
|
||
Config.BuildDir = build_dir
|
||
Config.resolve_paths(proj_dir)
|
||
Config.print_config()
|
||
|
||
# === --clean: 清理 temp、output 和 includes.binary 目录 ===
|
||
if _gb_clean:
|
||
if log is not None:
|
||
log.info("清理临时目录...", "clean")
|
||
if Config.TempDir is not None:
|
||
n: int = Utils.CleanDir(Config.TempDir)
|
||
if log is not None:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "%s: 删除 %d 个文件", Config.TempDir, n)
|
||
log.info(fb, "clean")
|
||
if Config.OutputDir is not None:
|
||
n2: int = Utils.CleanDir(Config.OutputDir)
|
||
if log is not None:
|
||
fb2: str = VLogger.fmt_buf()
|
||
if fb2 is not None:
|
||
viperlib.snprintf(fb2, 1024, "%s: 删除 %d 个文件", Config.OutputDir, n2)
|
||
log.info(fb2, "clean")
|
||
# 清理 includes.binary 目录(旧 .obj 文件会导致跳过重新编译)
|
||
mf_inc_bin_clean: str = Config.get_includes_binary_dir()
|
||
if mf_inc_bin_clean is not None:
|
||
n3: int = Utils.CleanDir(mf_inc_bin_clean)
|
||
if log is not None:
|
||
fb3: str = VLogger.fmt_buf()
|
||
if fb3 is not None:
|
||
viperlib.snprintf(fb3, 1024, "%s: 删除 %d 个文件", mf_inc_bin_clean, n3)
|
||
log.info(fb3, "clean")
|
||
|
||
# === --rebuild-includes: 删除 includes.binary 预编译缓存 ===
|
||
# 强制下次编译时重新编译所有 includes(不清 temp/output)
|
||
if args.get_bool("rebuild-includes"):
|
||
if log is not None:
|
||
log.info("重建 includes 缓存...", "rebuild-includes")
|
||
mf_inc_bin_rb: str = Config.get_includes_binary_dir()
|
||
if mf_inc_bin_rb is not None:
|
||
n_rb: int = Utils.CleanDir(mf_inc_bin_rb)
|
||
if log is not None:
|
||
fb_rb: str = VLogger.fmt_buf()
|
||
if fb_rb is not None:
|
||
viperlib.snprintf(fb_rb, 1024, "%s: 删除 %d 个文件(includes 将重新编译)", mf_inc_bin_rb, n_rb)
|
||
log.info(fb_rb, "rebuild-includes")
|
||
|
||
# === --clear-cache: 清除 temp 目录缓存(.vpi/.stub.ll/.text.ll) ===
|
||
# 强制下次 Phase1 重新生成 stub(不清 output/includes.binary)
|
||
if args.get_bool("clear-cache"):
|
||
if log is not None:
|
||
log.info("清除 temp 缓存...", "clear-cache")
|
||
if Config.TempDir is not None:
|
||
n_cc: int = Utils.CleanDir(Config.TempDir)
|
||
if log is not None:
|
||
fb_cc: str = VLogger.fmt_buf()
|
||
if fb_cc is not None:
|
||
viperlib.snprintf(fb_cc, 1024, "%s: 删除 %d 个文件(.vpi/.stub.ll 缓存)", Config.TempDir, n_cc)
|
||
log.info(fb_cc, "clear-cache")
|
||
|
||
# === --phase 参数控制 ===
|
||
# phase=1: 仅 stub 分离(生成 .stub.ll/.text.ll),不编译
|
||
# phase=2: 仅 stub 合并 + 编译(跳过 stub 分离)
|
||
# phase=all: 两者都执行(默认)
|
||
phase_mode: str = phase
|
||
if phase_mode is None:
|
||
phase_mode = "all"
|
||
do_phase1: int = 0
|
||
do_phase2: int = 0
|
||
# 注意: 使用 string.strcmp 而非 ==,避免编译器 == 语义 bug
|
||
if string.strcmp(phase_mode, "1") == 0 or string.strcmp(phase_mode, "all") == 0:
|
||
do_phase1 = 1
|
||
if string.strcmp(phase_mode, "2") == 0 or string.strcmp(phase_mode, "all") == 0:
|
||
do_phase2 = 1
|
||
if log is not None:
|
||
fb3: str = VLogger.fmt_buf()
|
||
if fb3 is not None:
|
||
viperlib.snprintf(fb3, 1024, "模式: %s (phase1=%d phase2=%d)", phase_mode, do_phase1, do_phase2)
|
||
log.info(fb3, "phase")
|
||
|
||
# === Phase1: 扫描 includes(按需翻译)===
|
||
# 对 includes 目录中每个 .vp/.py 文件,若 stub 不存在则翻译并分离 stub
|
||
if do_phase1 != 0 and Config.IncludesDirs is not None:
|
||
ph1_temp: str = Config.TempDir
|
||
if ph1_temp is None:
|
||
ph1_temp = "."
|
||
if Phase1Main.RunPhase1(mb, Config.IncludesDirs, ph1_temp, log) != 0:
|
||
VLogger.error("Phase1 失败,终止编译", "Phase1")
|
||
return 1
|
||
# 如果仅 Phase1(不执行 Phase2),直接退出
|
||
if do_phase2 == 0:
|
||
if log is not None:
|
||
log.info("Phase1 完成,退出", "phase")
|
||
return 0
|
||
|
||
# AST 解析:如果指定了 --src 或 --project,读取文件并解析为 AST 树
|
||
src_path: str = args.get_str("src")
|
||
|
||
# 无 project 配置且未指定 --src:无法构建,fail-fast 退出。
|
||
# (非工作区执行时 CWD 无 project.vpj,proj_loaded=0;若继续执行,
|
||
# 未初始化的 Config 字段可能为垃圾指针 → is not None 检查通过 →
|
||
# 用非法路径 → 崩溃且 ASan 堆栈来不及输出)
|
||
if proj_loaded == 0 and src_path is None:
|
||
if log is not None:
|
||
log.error("未找到 project.vpj(当前目录/上级目录)且未指定 --src,无法构建", "config")
|
||
argparse.release(args)
|
||
return 1
|
||
|
||
# === 多文件项目模式 ===
|
||
# 当 --project 指定但 --src 未指定时,扫描 source_dir 下所有 .vp/.py 文件编译
|
||
if src_path is None and project is not None and Config.SourceDir is not None:
|
||
mf_temp: str = Config.TempDir if Config.TempDir is not None else "."
|
||
mf_output: str = Config.OutputDir if Config.OutputDir is not None else "."
|
||
mf_cc: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
|
||
mf_cc_flags: str = Config.CompilerFlags if Config.CompilerFlags is not None else "-filetype=obj -relocation-model=pic"
|
||
mf_linker: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
|
||
mf_linker_flags: str = Config.LinkerFlags if Config.LinkerFlags is not None else "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition"
|
||
mf_linker_out: str = Config.LinkerOutput if Config.LinkerOutput is not None else "app.exe"
|
||
mf_includes_bin: str = Config.get_includes_binary_dir()
|
||
|
||
mf_ret: int = Phase2.RunMultiFileProject(
|
||
mb, Config.SourceDir, mf_temp, mf_output,
|
||
mf_cc, mf_cc_flags, mf_linker, mf_linker_flags, mf_linker_out,
|
||
mf_includes_bin, Config.IncludesDirs, do_phase1, do_phase2, log, args)
|
||
argparse.release(args)
|
||
if log is not None:
|
||
log.success("TransPyV 完成")
|
||
return mf_ret
|
||
|
||
# === 单文件模式:--src 指定时使用指定文件;--project 但无 SourceDir 时回退 ===
|
||
if src_path is None and project is not None:
|
||
if Config.SourceDir is not None:
|
||
# 构造入口路径: SourceDir/main.vp(不再做 vp -> py 后缀识别回退)
|
||
sd_len: t.CSizeT = 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
|
||
if log is not None:
|
||
fb4: str = VLogger.fmt_buf()
|
||
if fb4 is not None:
|
||
viperlib.snprintf(fb4, 1024, "入口文件: %s", src_path)
|
||
log.info(fb4, "project")
|
||
if src_path is not None:
|
||
if log is not None:
|
||
log.banner("AST 解析")
|
||
# 打开文件
|
||
f: fileio.File | t.CPtr = fileio.File(src_path, fileio.MODE.R)
|
||
if f.closed:
|
||
if log is not None:
|
||
fb5: str = VLogger.fmt_buf()
|
||
if fb5 is not None:
|
||
viperlib.snprintf(fb5, 1024, "无法打开文件: %s", src_path)
|
||
log.error(fb5, "fileio")
|
||
argparse.release(args)
|
||
return 1
|
||
# 分配源代码缓冲区
|
||
src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE)
|
||
if src_buf is None:
|
||
if log is not None:
|
||
log.error("malloc for src_buf failed", "memhub")
|
||
f.close()
|
||
argparse.release(args)
|
||
return 1
|
||
# 读取文件内容
|
||
bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE)
|
||
f.close()
|
||
if bytes_read < 0:
|
||
if log is not None:
|
||
fb6: str = VLogger.fmt_buf()
|
||
if fb6 is not None:
|
||
viperlib.snprintf(fb6, 1024, "读取文件失败 (错误码: %d)", bytes_read)
|
||
log.error(fb6, "fileio")
|
||
argparse.release(args)
|
||
return 1
|
||
# 添加 null 终止符,确保 strlen 和 SHA1 计算正确
|
||
if bytes_read < SRC_BUF_SIZE:
|
||
src_buf[bytes_read] = 0
|
||
else:
|
||
src_buf[SRC_BUF_SIZE - 1] = 0
|
||
if log is not None:
|
||
log.info("文件读取完成", "fileio")
|
||
fb7: str = VLogger.fmt_buf()
|
||
if fb7 is not None:
|
||
viperlib.snprintf(fb7, 1024, "读取 %d 字节", bytes_read)
|
||
log.info(fb7, "fileio")
|
||
# 解析 AST(拆分为词法+语法两阶段,便于调试)
|
||
ast._init_tables(mb)
|
||
lx: ast.Lexer | t.CPtr = ast.new_lexer(mb)
|
||
ast._lexer_init(lx, src_buf, mb)
|
||
tokens: ast.Token | t.CPtr = ast.tokenize(lx)
|
||
tree: ast.AST | t.CPtr = ast.parse_tokens(mb, tokens)
|
||
if tree is None:
|
||
if log is not None:
|
||
log.error("AST 解析失败", "ast")
|
||
argparse.release(args)
|
||
return 1
|
||
|
||
# === 翻译 AST → LLVM IR ===
|
||
if log is not None:
|
||
log.banner("LLVM IR 翻译")
|
||
tr: HandlesTranslator.Translator | t.CPtr = HandlesTranslator.Translator()
|
||
result: int = tr.translate(tree)
|
||
if result == 0:
|
||
IR_BUF_SIZE: t.CSizeT = 262144
|
||
ir_buf: bytes = stdlib.malloc(IR_BUF_SIZE)
|
||
if ir_buf is not None:
|
||
tr.dump_ir(ir_buf, IR_BUF_SIZE, llvmlite.OUTPUT_FULL)
|
||
|
||
# === 编译管线: .ll → .obj → .exe ===
|
||
if log is not None:
|
||
log.banner("编译管线")
|
||
|
||
# module_name 用源代码内容的 SHA1(前 16 字符),与 Projectrans.py 一致
|
||
# output_name 从文件名推导(如 test.vp -> test.exe),保持可读性
|
||
module_name: str = Utils.compute_sha1(mb, src_buf)
|
||
if module_name is None:
|
||
VLogger.error("SHA1 计算失败", "main")
|
||
output_name: str = "test_prog.exe"
|
||
# 从文件名提取 output_name(去掉路径和 .vp/.py 扩展名)
|
||
src_len: t.CSizeT = string.strlen(src_path)
|
||
if src_len > 0:
|
||
# 找到最后一个 / 或 \ 之后的部分
|
||
base_start: t.CSizeT = src_len
|
||
i: t.CSizeT = src_len
|
||
while i > 0:
|
||
i -= 1
|
||
ch: t.CChar = src_path[i]
|
||
if ch == '/' or ch == '\\':
|
||
base_start = i + 1
|
||
break
|
||
if i == 0:
|
||
base_start = 0
|
||
# 找到 .vp/.py 扩展名
|
||
base_end: t.CSizeT = src_len
|
||
j: t.CSizeT = base_start
|
||
while j < src_len:
|
||
if src_path[j] == '.':
|
||
base_end = j
|
||
break
|
||
j += 1
|
||
# 输出名 = 文件名 + ".exe"
|
||
name_len: t.CSizeT = base_end - base_start
|
||
if name_len > 0:
|
||
out_len: t.CSizeT = name_len + 5
|
||
output_name = stdlib.malloc(out_len)
|
||
if output_name is not None:
|
||
# 手动复制文件名并追加 .exe(viperlib.snprintf 不支持 %.*s,string 无 strcat)
|
||
string.strncpy(output_name, src_path + base_start, name_len)
|
||
output_name[name_len] = '.'
|
||
output_name[name_len + 1] = 'e'
|
||
output_name[name_len + 2] = 'x'
|
||
output_name[name_len + 3] = 'e'
|
||
output_name[name_len + 4] = '\0'
|
||
# 使用 project.vpj 中的配置
|
||
temp_dir: str = Config.TempDir if Config.TempDir is not None else "."
|
||
output_dir: str = Config.OutputDir if Config.OutputDir is not None else "."
|
||
# 设置全局 temp_dir(供跨模块 CDefine 查找使用)
|
||
GenApi.set_temp_dir(temp_dir)
|
||
cc_cmd: str = Config.CompilerCmd if Config.CompilerCmd is not None else "llc"
|
||
cc_flags: str = "-filetype=obj -relocation-model=pic"
|
||
linker_cmd: str = Config.LinkerCmd if Config.LinkerCmd is not None else "clang++"
|
||
linker_flags: str = "-lmsvcrt -lucrt -lpthread -lmingwex -lkernel32 -lgcc -Wl,--allow-multiple-definition"
|
||
# --project 模式且未显式指定 --src 时:使用 config 中的 linker_output(如 TransPyV.exe)
|
||
# 显式指定 --src 时:使用从文件名推导的 output_name(如 test.exe),避免覆盖正在运行的 exe
|
||
if project is not None and Config.LinkerOutput is not None and args.get_str("src") is None:
|
||
linker_output: str = Config.LinkerOutput
|
||
else:
|
||
linker_output: str = output_name
|
||
|
||
# 计算 includes.binary 目录路径(链接时附加预编译 .obj)
|
||
includes_binary_dir: str = Config.get_includes_binary_dir()
|
||
|
||
ir_len: t.CSizeT = string.strlen(ir_buf)
|
||
|
||
# (--phase 参数已在配置加载后处理,此处直接使用 do_phase1/do_phase2)
|
||
|
||
# === Phase1 stub 分离:使用 OUTPUT_STUB/TEXT 模式分别生成 stub.ll 和 text.ll ===
|
||
if do_phase1 != 0 and temp_dir is not None and module_name is not None:
|
||
if log is not None:
|
||
log.banner("stub 分离")
|
||
SF_IR_SIZE: t.CSizeT = 262144
|
||
td_len_sf: t.CSizeT = string.strlen(temp_dir)
|
||
# 保存 stub.ll
|
||
sf_stub_buf: bytes = stdlib.malloc(SF_IR_SIZE)
|
||
if sf_stub_buf is not None:
|
||
tr.dump_ir(sf_stub_buf, SF_IR_SIZE, llvmlite.OUTPUT_STUB)
|
||
sf_stub_len: t.CSizeT = string.strlen(sf_stub_buf)
|
||
sf_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "stub.ll")
|
||
if sf_stub_path is not None:
|
||
sf_f: fileio.File | t.CPtr = fileio.File(sf_stub_path, fileio.MODE.W)
|
||
if not sf_f.closed:
|
||
sf_f.write(sf_stub_buf, sf_stub_len)
|
||
sf_f.close()
|
||
stdlib.free(sf_stub_path)
|
||
stdlib.free(sf_stub_buf)
|
||
# 保存 text.ll
|
||
sf_text_buf: bytes = stdlib.malloc(SF_IR_SIZE)
|
||
if sf_text_buf is not None:
|
||
tr.dump_ir(sf_text_buf, SF_IR_SIZE, llvmlite.OUTPUT_TEXT)
|
||
sf_text_len: t.CSizeT = string.strlen(sf_text_buf)
|
||
sf_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_sf, module_name, "text.ll")
|
||
if sf_text_path is not None:
|
||
sf_tf: fileio.File | t.CPtr = fileio.File(sf_text_path, fileio.MODE.W)
|
||
if not sf_tf.closed:
|
||
sf_tf.write(sf_text_buf, sf_text_len)
|
||
sf_tf.close()
|
||
stdlib.free(sf_text_path)
|
||
stdlib.free(sf_text_buf)
|
||
|
||
# phase=1 模式:仅生成 stub,不执行编译
|
||
if do_phase2 == 0:
|
||
if log is not None:
|
||
log.success("Phase1 完成(仅 stub 分离,跳过编译)")
|
||
argparse.release(args)
|
||
return 0
|
||
|
||
# === Phase2: 组合本地 stub + 依赖 stubs + 本地 text → 完整 IR ===
|
||
final_ir: bytes = ir_buf
|
||
final_ir_len: t.CSizeT = ir_len
|
||
if temp_dir is not None and module_name is not None:
|
||
if log is not None:
|
||
log.banner("IR 组合")
|
||
SF_COMBINED_SIZE: t.CSizeT = 4194304 # 4MB
|
||
combined_buf: bytes = stdlib.malloc(SF_COMBINED_SIZE)
|
||
if combined_buf is not None:
|
||
combined_len: t.CSizeT = StubMergerBuild.BuildCombinedIR(temp_dir, module_name, combined_buf, SF_COMBINED_SIZE)
|
||
if combined_len > 0:
|
||
final_ir = combined_buf
|
||
final_ir_len = combined_len
|
||
else:
|
||
stdlib.free(combined_buf)
|
||
VLogger.error("BuildCombinedIR 失败,终止编译", "stub")
|
||
|
||
br: BuildPipeline.BuildResult | t.CPtr = BuildPipeline.run_pipeline(
|
||
final_ir, final_ir_len, temp_dir, output_dir, module_name,
|
||
cc_cmd, cc_flags, linker_cmd, linker_flags, linker_output,
|
||
includes_binary_dir
|
||
)
|
||
if br is not None and br.Success == 1:
|
||
if log is not None:
|
||
log.success("编译管线完成")
|
||
fb8: str = VLogger.fmt_buf()
|
||
if fb8 is not None:
|
||
viperlib.snprintf(fb8, 1024, "输出: %s/%s", output_dir, linker_output)
|
||
log.info(fb8)
|
||
# --run 模式:执行生成的可执行文件
|
||
# phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run
|
||
if args.get_bool("run") and BuildPipeline._phase2_cmd_path is None:
|
||
exe_path: bytes = stdlib.malloc(string.strlen(output_dir) + string.strlen(linker_output) + 2)
|
||
if exe_path is not None:
|
||
viperlib.snprintf(exe_path, 256, "%s/%s", output_dir, linker_output)
|
||
if log is not None:
|
||
fb9: str = VLogger.fmt_buf()
|
||
if fb9 is not None:
|
||
viperlib.snprintf(fb9, 1024, "执行: %s", exe_path)
|
||
log.info(fb9, "run")
|
||
r: subprocess.CompletedProcess | t.CPtr = subprocess.run(exe_path, False, False)
|
||
if r is not None:
|
||
if log is not None:
|
||
fb10: str = VLogger.fmt_buf()
|
||
if fb10 is not None:
|
||
viperlib.snprintf(fb10, 1024, "退出码: %d", r.returncode)
|
||
log.info(fb10, "run")
|
||
else:
|
||
if log is not None:
|
||
log.error("编译管线失败", "pipeline")
|
||
if br is not None and br.ErrorMsg is not None:
|
||
fb11: str = VLogger.fmt_buf()
|
||
if fb11 is not None:
|
||
viperlib.snprintf(fb11, 1024, "编译管线失败: %s", br.ErrorMsg)
|
||
log.error(fb11, "FATAL")
|
||
sys.exit(1)
|
||
else:
|
||
if log is not None:
|
||
log.error("malloc for IR buffer failed", "memhub")
|
||
else:
|
||
if log is not None:
|
||
log.error("翻译失败", "translator")
|
||
|
||
argparse.release(args)
|
||
if log is not None:
|
||
log.success("TransPyV 完成")
|
||
return 0
|