- LspPositions:UTF-8 字节列 / UTF-16 code unit 转换,支持 LF/CRLF/emoji - LspDocuments:didOpen/didChange/didClose/didSave,增量与全量替换,双池复用 - LspWorkspace:file URI 解码、project.vpj 向上查找、source_dir/includes - LspServer:请求池 mark/reset、textDocumentSync、viper/lspStats 与 viper/lspDocument - probe --gate1:文档编辑、UTF-16 代理对、10k 次编辑与池稳定性 - 依赖 includes/memhub 的 NewMemBuddy 堆构造工厂(单独提交 fa7591e)
581 lines
33 KiB
Plaintext
581 lines
33 KiB
Plaintext
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Repl as Repl
|
||
import lib.core.SelfBuild as SelfBuild
|
||
import lib.lsp.LspServer as LspServer
|
||
import lib.lsp.LspTransport as LspTransport
|
||
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
|
||
import stdio
|
||
import string
|
||
import stdlib
|
||
import memhub
|
||
import argparse
|
||
import platmacro
|
||
cif platmacro.IS_WINDOWS:
|
||
import win32.win32console as win32cmd
|
||
import win32.win32base as win32base
|
||
import sys
|
||
import ast
|
||
import os
|
||
import os._win32
|
||
import os._posix
|
||
import os.path
|
||
import shutil
|
||
import mlirlite
|
||
import subprocess
|
||
import lib.Phase.Phase1.Phase1Main as Phase1Main
|
||
|
||
|
||
# Main Pool 1 GiB(不要简单调大:2 GiB 会让 MemBuddy 内部索引/位宽溢出 ——
|
||
# ASan: access-violation at memhub.vp:858 _fl_push,就在 __init__ 阶段。
|
||
# 池耗尽的正解是修泄漏:mlirlite 全局数组 dense 渲染的"每元素 ~5KB 不复用",见 includes e9e1933。)
|
||
define POOL_SIZE = 1073741824
|
||
# Permanent memory pool size: 2^29 + 264 = 512MB usable, then change to 1 GiB
|
||
define PERM_POOL_SIZE = 1073741824
|
||
# LSP mode uses smaller pools: the language server does not run code generation.
|
||
define LSP_POOL_SIZE = 67108864
|
||
define LSP_PERM_POOL_SIZE = 33554432
|
||
# 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_VPC_VERSION: const __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_VPC_COMMIT = "ab0f37e1a8"
|
||
define V_RUNTIME_COMMIT = "f087aa88e2"
|
||
define V_TARGET = "x86_64-w64-windows-gnu"
|
||
define V_LLVM = "22.1.8"
|
||
# --repl template-project lookup buffers (repl.vpj path / miss diagnostic text)
|
||
define REPL_PROJ_PATH_CAP = 1024
|
||
define REPL_PROJ_DIAG_CAP = 2048
|
||
|
||
|
||
# main: Command-line entry
|
||
def main(argc: int, argv: []str) -> int:
|
||
detect_flag: int = 0
|
||
cmd_line_d: str
|
||
deep_detect_flag: int = 0
|
||
lsp_flag: int = 0
|
||
cif platmacro.IS_WINDOWS:
|
||
win32cmd.SetConsoleOutputCP(CODE_PAGE)
|
||
win32cmd.SetConsoleCP(CODE_PAGE)
|
||
|
||
for i in range(argc):
|
||
if not argv[i]: break
|
||
if string.strstr(argv[i], "--detection") is not None: detect_flag = 1
|
||
if string.strstr(argv[i], "--detection-deep") is not None: deep_detect_flag = 1
|
||
if string.strstr(argv[i], "--lsp") is not None: lsp_flag = 1
|
||
|
||
|
||
# [MEMDBG] free-list walk diagnostics are off by default (each free would walk the whole free
|
||
# list of its order); --detection / --detection-deep turn them back on with the other checks.
|
||
if detect_flag != 0 or deep_detect_flag != 0: memhub._memdbg_enable = 1
|
||
|
||
# Initialize the mbuddy memory pool
|
||
pool_size: sizet = sizet(POOL_SIZE)
|
||
perm_size: sizet = sizet(PERM_POOL_SIZE)
|
||
if lsp_flag != 0:
|
||
pool_size = sizet(LSP_POOL_SIZE)
|
||
perm_size = sizet(LSP_PERM_POOL_SIZE)
|
||
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_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_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)
|
||
|
||
# set the global mbuddy pointer (needed by sys and 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
|
||
# Runtime-dep deploy primitives (BuildPipeline.deploy_runtime_deps): os.mkdir + shutil.copyfile.
|
||
os._mbuddy = mb
|
||
os._win32._mbuddy = mb
|
||
os._posix._mbuddy = mb
|
||
os.path._mbuddy = mb
|
||
shutil._mbuddy = mb
|
||
# hashlib._mbuddy = (mb)
|
||
VLogger._mbuddy = mb
|
||
lib._mbuddy = mb
|
||
|
||
# P2: create the project-level Generator singleton (reused across Phase1/Phase2/BuildPipeline;
|
||
gen: 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: inject module-level Handles singletons (Phase1 calls their state funcs); module globals carry sha1 prefixes, assign each
|
||
GenApi._g_gen = gen
|
||
# P6: inject statement/assign/var/import/translator-driver module singletons
|
||
|
||
# initialize VLogger and print the startup log
|
||
VLogger.init_logger()
|
||
log: VLogger.Logger* = VLogger.logger
|
||
if log is None: log.error("ViperC Failed to parse parameters")
|
||
|
||
# initialize command-line args (Windows: GetCommandLineA, POSIX: /proc/self/cmdline)
|
||
sys._init_argv()
|
||
|
||
# create the argument parser
|
||
parser: argparse.ArgumentParser = argparse.ArgumentParser("ViperC", "ViperC command-line parameter parser", pool=mb)
|
||
|
||
# register arguments (matching 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)")
|
||
# Registered only so argparse accepts the flag: the value is read via raw
|
||
# command-line strstr before MemBuddy construction, same as --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("--lsp", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Start the LSP server over stdio (JSON-RPC 2.0)")
|
||
parser.add_argument("--build-self", None, argparse.BOOL, 0, None, False, argparse.STORE_TRUE, "Compile ViperC into __vpcbuild_2__ and launch copyself to replace __vpcbuild__")
|
||
parser.add_argument("--build-dir", None, argparse.STRING, 0, None, False, argparse.STORE, "Override project.vpj build_dir (default ./__vpcbuild__; 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")
|
||
|
||
# parse command-line arguments
|
||
# 必须用指针:parse_args 返回 ParsedArgs*,失败时返回 NULL。声明成值类型会让编译器
|
||
# 生成"从返回指针处拷贝 struct"的代码(`mov rax,[rcx]`),NULL 时直接 AV;
|
||
# 同时 `args is None` 会退化成对栈地址判空(恒不为真),错误处理永远进不去。
|
||
args: argparse.ParsedArgs* = parser.parse_args(sys._argc, sys._argv)
|
||
if args is None:
|
||
log.error("Failed to parse arguments", "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 ViperC %s
|
||
Author: terminexus@gvsds.com
|
||
|
||
Tpv commit: %s
|
||
Tpc commit: %s
|
||
VRTE commit: %s
|
||
Target: %s
|
||
|
||
""", V_VIPER_VERSION, V_RUNTIME_VERSION, V_VPC_VERSION, V_VPC_COMMIT, V_TPC_COMMIT, V_RUNTIME_COMMIT, V_TARGET, V_LLVM)
|
||
return 0
|
||
|
||
# --lsp is a standalone stdio server. It must not share stdout with any build
|
||
# or diagnostic mode; protocol frames are the only stdout output.
|
||
if args.get_bool("lsp"):
|
||
if args.get_str("project") is not None or args.get_str("src") is not None or args.get_str("phase") is not None or args.get_bool("run") or args.get_bool("clean") or args.get_bool("repl") or args.get_bool("build-self") or args.get_bool("asan") or args.get_bool("traceback") or args.get_bool("rebuild-includes") or args.get_bool("clear-cache") or args.get_bool("nowarn-register") or args.get_bool("no-llvmpeephole") or args.get_bool("show-args") or args.get_str("phase2command") is not None or args.get_str("build-dir") is not None or args.get_str("output") is not None or args.get_str("temp") is not None or args.get_str("cc") is not None or detect_flag != 0 or deep_detect_flag != 0:
|
||
LspTransport.StderrWrite("FAIL: --lsp is a standalone mode; do not combine it with build or diagnostic options\n")
|
||
argparse.release(args)
|
||
return 2
|
||
argparse.release(args)
|
||
return LspServer.Run(mb, perm_pool)
|
||
|
||
# --build-self is a standalone mode: it compiles this project into __vpcbuild_2__,
|
||
# then launches copyself to replace __vpcbuild__ after a delay.
|
||
if args.get_bool("build-self"):
|
||
if args.get_str("project") is not None or args.get_str("src") is not None or args.get_str("phase") is not None or args.get_bool("run") or args.get_bool("clean") or args.get_bool("repl") or args.get_bool("asan") or args.get_bool("traceback") or args.get_bool("rebuild-includes") or args.get_bool("clear-cache") or args.get_bool("nowarn-register") or args.get_bool("no-llvmpeephole") or args.get_str("phase2command") is not None or args.get_str("build-dir") is not None or args.get_str("output") is not None or args.get_str("temp") is not None or args.get_str("cc") is not None:
|
||
stdio.printf("FAIL: --build-self is a standalone mode; do not combine it with build options\n")
|
||
argparse.release(args)
|
||
return 2
|
||
argparse.release(args)
|
||
return SelfBuild.RunSelfBuild()
|
||
|
||
# --repl guard: mutually exclusive with --phase2command / --asan, checked first
|
||
# before their global-state/cleanup side effects; both would break fragment DLLs.
|
||
repl_flag: bool = args.get_bool("repl")
|
||
if repl_flag:
|
||
if args.get_str("phase2command") is not None:
|
||
stdio.printf("FAIL: --repl and --phase2command are mutually exclusive\n")
|
||
stdio.printf(" Reason: under --phase2command, compile_ll_to_obj only writes the bat and does not run llc,\n")
|
||
stdio.printf(" so the fragment .obj is never produced and the subsequent clang++ -shared necessarily fails\n")
|
||
stdio.printf(" Fix: drop --phase2command (REPL must produce and load the fragment DLL immediately)\n")
|
||
argparse.release(args)
|
||
return 2
|
||
if args.get_bool("asan"):
|
||
stdio.printf("FAIL: --repl and --asan are mutually exclusive\n")
|
||
stdio.printf(" Reason: --asan first instruments the fragment .ll with ASan, but the fragment artifact is a runtime\n")
|
||
stdio.printf(" DLL loaded by LoadLibraryA, and the host process does not link the ASan runtime; instrumentation semantics are undefined\n")
|
||
stdio.printf(" Fix: drop --asan (to debug memory issues, build an ASan ViperC in project mode)\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 and --traceback are mutually exclusive\n")
|
||
stdio.printf(" Reason: --traceback relies on startup-ctor injection + a per-module DWARF side file,\n")
|
||
stdio.printf(" but the fragment is a runtime LoadLibraryA-loaded DLL; the two semantics are undefined\n")
|
||
stdio.printf(" Fix: drop --traceback (for crash stacks, build a --traceback ViperC in project mode)\n")
|
||
argparse.release(args)
|
||
return 2
|
||
|
||
# === --repl: M1 interactive loop (expression fragment -> DLL -> echo). Runs
|
||
# after project-config load and before Phase1/Phase2, then returns.
|
||
|
||
# print parse result
|
||
# log.banner("ViperC argument parse result")
|
||
|
||
# string arguments
|
||
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 and --src are mutually exclusive: --project uses project.vpj,
|
||
# --src uses viperbuild built-in config; giving both fails fast.
|
||
if project is not None and src is not None:
|
||
stdio.printf("FAIL: --project and --src are mutually exclusive\n")
|
||
stdio.printf(" Reason: --project uses project.vpj project mode, --src uses the entry file's built-in viperbuild config\n")
|
||
stdio.printf(" Fix: choose one; in --src mode the build config goes in 'import viperbuild' within the entry file's dependency closure\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: (not specified)\n")
|
||
if src is not None: stdio.printf(" --src: %s\n", src)
|
||
else: stdio.printf(" --src: (not specified)\n")
|
||
if temp is not None: stdio.printf(" --temp: %s\n", temp)
|
||
else: stdio.printf(" --temp: (not specified)\n")
|
||
if output is not None: stdio.printf(" --output: %s\n", output)
|
||
else: stdio.printf(" --output: (not specified)\n")
|
||
if phase is not None: stdio.printf(" --phase: %s\n", phase)
|
||
else: stdio.printf(" --phase: (not specified, default all)\n")
|
||
if cc is not None: stdio.printf(" --cc: %s\n", cc)
|
||
else: stdio.printf(" --cc: (not specified)\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("Argument parsing complete.\n\n")
|
||
|
||
# Mode switches: isolate includes caches by suffix so mode-specific objects do
|
||
# not mix (ASan/traceback objects are incompatible); suffix combines both flags.
|
||
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 mode enabled (opt -passes=asan instrumentation; runtime libs must be declared in project.vpj)", "asan")
|
||
if gb_traceback: log.info("Traceback mode enabled (llc keep frame pointer + link dbghelp + Install ctor + <exe>.stackmap)", "traceback")
|
||
# Enabling either mode clears that suffix's includes cache to force a rebuild
|
||
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"Mode cache cleanup: {mf_inc_bin_mode} deleted {n_mode} files (will rebuild with {Config._inc_bin_suffix})", "mode")
|
||
|
||
# === --phase2command: only write llc/link commands to a bat, do not execute them ===
|
||
p2c: str = args.get_str("phase2command")
|
||
if p2c is not None:
|
||
BuildPipeline._phase2_cmd_path = p2c
|
||
log.info(f"--phase2command: not running llc/link, commands written to {p2c}", "phase2cmd")
|
||
|
||
# --nowarn-register: suppress Phase A dependency WARNs (cross-module text.ll not yet generated)
|
||
if args.get_bool("nowarn-register"):
|
||
VLogger._nowarn_register = 1
|
||
log.info("--nowarn-register enabled: suppress register-class WARN output", "vlogger")
|
||
|
||
# --no-llvmpeephole: disable all local peepholes (T2/T3); FunctionPrint serializes directly
|
||
if args.get_bool("no-llvmpeephole"):
|
||
mlirlite.G_PeepEnable = 0
|
||
log.info("--no-llvmpeephole enabled: disable FunctionPrint local peepholes (RenumberSSA kept)", "peep")
|
||
|
||
# === --repl convenience: without --project, search cwd then the exe dir for
|
||
# repl.vpj and feed it to Config.Load_project_config; miss fails fast.
|
||
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 failed to allocate the path buffer for locating 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 not given, using the located template project {proj_path}", "repl")
|
||
else:
|
||
stdio.printf("FAIL: --repl could not find the project file repl.vpj\n")
|
||
stdio.printf("%s", repl_diag_buf)
|
||
stdio.printf(" You can also pass --project to specify the project file explicitly (e.g. ViperC/project.vpj in this repo)\n")
|
||
argparse.release(args)
|
||
return 1
|
||
# load project config (explicit --project / repl.vpj found by --repl)
|
||
if proj_path is not None:
|
||
log.banner("Project config")
|
||
cfg_ret: int = Config.Load_project_config(proj_path)
|
||
if cfg_ret == 0: proj_loaded = 1
|
||
else: log.warning(f"Failed to load project.vpj: {proj_path}", "config")
|
||
else:
|
||
# try the default project.vpj (current and parent dir); --src mode must not
|
||
# implicitly load project.vpj (it would pollute the config source).
|
||
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:
|
||
# extract project.vpj's dir so relative paths resolve against it
|
||
proj_len: sizet = string.strlen(proj_path)
|
||
slash_pos: sizet = proj_len
|
||
for i in range(int(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] overrides build_dir before resolve_paths so paths stay project_dir-based
|
||
if build_dir is not None: Config.BuildDir = build_dir
|
||
Config.resolve_paths(proj_dir)
|
||
Config.print_config()
|
||
|
||
# === --src: viperbuild built-in config discovery/apply (Pass 0), before the
|
||
# switches read Config; a miss or bad config fails fast.
|
||
if src is not None:
|
||
log.banner("viperbuild")
|
||
if ViperBuild.DiscoverAndApply(mb, src) != 0:
|
||
argparse.release(args)
|
||
return 1
|
||
Config.print_config()
|
||
# proj.advanced.asan / .traceback 等价 CLI 的 --asan / --traceback(两者取并集)。
|
||
# 必须在 Phase1/Phase2 之前应用:includes 缓存后缀要与模式一起切换。
|
||
adv_asan: bool = ViperBuild.AdvancedAsan() != 0
|
||
adv_traceback: bool = ViperBuild.AdvancedTraceback() != 0
|
||
if adv_asan or adv_traceback:
|
||
if args.get_bool("repl"):
|
||
log.error("--repl is mutually exclusive with proj.advanced.asan / .traceback:", "viperbuild")
|
||
log.error(" a fragment is a runtime LoadLibraryA DLL; ASan instrumentation and traceback ctor/DWARF semantics are undefined", "viperbuild")
|
||
argparse.release(args)
|
||
return 2
|
||
prev_asan: bool = BuildPipeline._asan_mode != 0
|
||
prev_traceback: bool = BuildPipeline._traceback_mode != 0
|
||
if adv_asan: BuildPipeline._asan_mode = True
|
||
if adv_traceback: BuildPipeline._traceback_mode = True
|
||
now_asan: bool = BuildPipeline._asan_mode != 0
|
||
now_traceback: bool = BuildPipeline._traceback_mode != 0
|
||
if now_asan and now_traceback: Config._inc_bin_suffix = ".binary_traceback_asan"
|
||
elif now_traceback: Config._inc_bin_suffix = ".binary_traceback"
|
||
elif now_asan: Config._inc_bin_suffix = ".binary_asan"
|
||
else: Config._inc_bin_suffix = ".binary"
|
||
if adv_asan: log.info("ASan mode enabled by viperbuild (proj.advanced.asan)", "asan")
|
||
if adv_traceback: log.info("Traceback mode enabled by viperbuild (proj.advanced.traceback)", "traceback")
|
||
if now_asan != prev_asan or now_traceback != prev_traceback:
|
||
mf_inc_adv: str = Config.get_includes_binary_dir()
|
||
if mf_inc_adv is not None:
|
||
n_adv: int = Utils.CleanDir(mf_inc_adv)
|
||
log.info(f"Mode cache cleanup: {mf_inc_adv} deleted {n_adv} files (will rebuild with {Config._inc_bin_suffix})", "mode")
|
||
|
||
# === --repl: M1 interactive loop (fragment -> IR -> llc -> clang++ -shared ->
|
||
# DLL -> echo), after config load and before Phase1/Phase2.
|
||
if args.get_bool("repl"):
|
||
Repl._perm_pool = perm_pool # jmp_buf/parse-table/session buffers must live across calls; must use PermPool
|
||
if log is not None:
|
||
log.set_jit_flag(True) # errors longjmp back to REPL (reset to False when Repl.run exits)
|
||
rc_repl: int = Repl.run(mb)
|
||
argparse.release(args)
|
||
return rc_repl
|
||
|
||
# --asan requires the project to declare the ASan runtime in project.vpj linker.static_libs (no hardcoded paths); missing fails fast
|
||
if BuildPipeline._asan_mode != 0 and Config.asan_runtime_declared() == 0:
|
||
log.error("Enabling --asan requires declaring the ASan runtime in project.vpj's linker.static_libs:", "asan")
|
||
log.error(" <MSYS2>/clang64/lib/clang/<ver>/lib/windows/libclang_rt.asan_dynamic-x86_64.dll.a", "asan")
|
||
log.error(" <MSYS2>/clang64/lib/clang/<ver>/lib/windows/libclang_rt.asan_dynamic_runtime_thunk-x86_64.a", "asan")
|
||
log.error("The clang64 root is auto-detected from PATH (see README section 8.1)", "asan")
|
||
return 1
|
||
|
||
# === --clean: remove temp, output and includes.binary directories ===
|
||
if _gb_clean:
|
||
log.info("Cleaning temp directory...", "clean")
|
||
if Config.TempDir is not None:
|
||
n: int = Utils.CleanDir(Config.TempDir)
|
||
log.info(f"{Config.TempDir}: deleted {n} files", "clean")
|
||
if Config.OutputDir is not None:
|
||
n2: int = Utils.CleanDir(Config.OutputDir)
|
||
log.info(f"{Config.OutputDir}: deleted {n2} files", "clean")
|
||
# Clean the includes.binary dir (stale .obj files would skip recompilation)
|
||
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}: deleted {n3} files", "clean")
|
||
|
||
# === --rebuild-includes: delete the includes.binary precompile cache ===
|
||
# Forces all includes to recompile next time (temp/output untouched)
|
||
if args.get_bool("rebuild-includes"):
|
||
log.info("Rebuilding includes cache...", "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}: deleted {n_rb} files (includes will be recompiled)", "rebuild-includes")
|
||
|
||
# === --clear-cache: clear the temp dir cache (.vpi/.stub.ll/.text.ll) ===
|
||
# Forces Phase1 to regenerate stubs next time (output/includes.binary untouched)
|
||
if args.get_bool("clear-cache"):
|
||
log.info("Clearing temp cache...", "clear-cache")
|
||
if Config.TempDir is not None:
|
||
n_cc: int = Utils.CleanDir(Config.TempDir)
|
||
log.info(f"{Config.TempDir}: deleted {n_cc} files (.vpi/.stub.ll cache)", "clear-cache")
|
||
|
||
# === viperbuild: proj.clean.* 细粒度清理 ===
|
||
# build_dir 指整个构建根(缺省 {source}/__vpcbuild__,含 temp + output),优先级高于
|
||
# 单列的 temp/output。--clean 已覆盖全部目录时跳过,避免重复删除与重复日志。
|
||
if src is not None and _gb_clean == False:
|
||
# clean.all 是总开关:等价于 build_dir + includes(temp/output 已被 build_dir 覆盖),
|
||
# 个体开关写 False 不能关掉它。
|
||
c_all: bool = ViperBuild.CleanAll() != 0
|
||
c_build_dir: bool = c_all or ViperBuild.CleanBuildDir() != 0
|
||
c_temp: bool = c_all or ViperBuild.CleanTemp() != 0
|
||
c_output: bool = c_all or ViperBuild.CleanOutput() != 0
|
||
c_includes: bool = c_all or ViperBuild.CleanIncludes() != 0
|
||
if c_build_dir:
|
||
if Config.BuildDir is not None:
|
||
n_cb: int = Utils.CleanDir(Config.BuildDir)
|
||
log.info(f"{Config.BuildDir}: deleted {n_cb} files (proj.clean.build_dir)", "viperbuild-clean")
|
||
else:
|
||
if c_temp and Config.TempDir is not None:
|
||
n_ct: int = Utils.CleanDir(Config.TempDir)
|
||
log.info(f"{Config.TempDir}: deleted {n_ct} files (proj.clean.temp)", "viperbuild-clean")
|
||
if c_output and Config.OutputDir is not None:
|
||
n_co: int = Utils.CleanDir(Config.OutputDir)
|
||
log.info(f"{Config.OutputDir}: deleted {n_co} files (proj.clean.output)", "viperbuild-clean")
|
||
if c_includes:
|
||
mf_inc_clean_vb: str = Config.get_includes_binary_dir()
|
||
if mf_inc_clean_vb is not None:
|
||
n_ci: int = Utils.CleanDir(mf_inc_clean_vb)
|
||
log.info(f"{mf_inc_clean_vb}: deleted {n_ci} files (proj.clean.includes)", "viperbuild-clean")
|
||
|
||
# === --phase: 1=stub split only; 2=stub merge+compile only; all=both (default) ===
|
||
phase_mode: str = phase
|
||
if phase_mode is None: phase_mode = "all"
|
||
do_phase1: int = 0
|
||
do_phase2: int = 0
|
||
# Note: use string.strcmp instead of == to avoid the compiler == semantics 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"Mode: {phase_mode} (phase1={do_phase1} phase2={do_phase2})", "phase")
|
||
|
||
# === Phase1: scan includes (on-demand translation) ===
|
||
# For each .vp/.py file under includes, translate and split the stub if absent
|
||
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 failed, aborting compilation", "Phase1")
|
||
return 1
|
||
# Phase1-only (no Phase2): exit right away
|
||
if do_phase2 == 0:
|
||
log.info("Phase1 complete, exiting", "phase")
|
||
return 0
|
||
|
||
# AST parse: when --src or --project is given, read the file and parse it into an AST
|
||
src_path: str = args.get_str("src")
|
||
|
||
# No project config and no --src: fail fast (uninitialized Config fields are garbage pointers -> crash)
|
||
if proj_loaded == 0 and src_path is None:
|
||
log.error("project.vpj not found (current/parent dir) and --src not given; cannot build", "config")
|
||
argparse.release(args)
|
||
return 1
|
||
|
||
# Project mode (multi-file): --project scans source_dir for all .vp; --src + viperbuild
|
||
# reuses it (Pass 0 writes config back, SourceDir as root); the two are mutually exclusive.
|
||
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("ViperC complete")
|
||
return mf_ret
|
||
|
||
# Reaching here means no buildable project root (SourceDir missing): fail fast, no silent "done"
|
||
log.error("No buildable project root: project.vpj is missing source_dir, or viperbuild provided no source", "config")
|
||
argparse.release(args)
|
||
return 1
|