# 12 - 编译系统 本章介绍 Viper 的编译系统:从源码到可执行产物的流水线、工程配置 `project.vpj`,以及**用 Viper 自己写构建配置**的元编程内建库 `viperbuild`。 ## 编译系统总览 Viper 目前有两条编译路径,二者共享同一套语言语义: | 编译器 | 实现语言 | 定位 | |--------|----------|------| | **TPC**(TransPyC) | Python | 种子编译器:纯 Python 实现,用于自举起点与快速试验 | | **VPC**(ViperC) | Viper | 自举产物:由 TPC 编译出的 Viper 原生编译器,日常构建走它 | 两阶段编译模型: - **Phase 1(声明阶段)**:从入口出发按 `import` 递归建依赖图 → 1a 注册 struct/enum/union → 1b-pre 生成 `.vpi` 签名存根 → 1b 全量翻译生成 `.stub.ll`(声明)。 - **Phase 2(翻译与链接)** - **Phase A-pre-vpi / Phase A-pre**:加载依赖 `.vpi`,预翻译准备 - **Phase A**:主循环多遍收敛翻译,`.stub.ll` / `.text.ll` 在此写盘 - **Phase B**:`llc` 并行编译 `.ll` → `.obj` - **Phase B+**:缺失 includes 按需编译 - **Phase C**:链接(链接器由工程配置的 `linker.cmd` 决定) ### SHA1 命名空间与增量编译 - 每个源文件按内容算 SHA1(取前 16 个十六进制字符);`temp` 下按 `{temp}/{sha1[0:2]}/` 切片存放 `.vpi` / `.stub.ll` / `.text.ll` / `.deps.txt`。 - **命名空间**:未 `export` 的函数与结构体带 SHA1 前缀,消除跨模块符号冲突。 - **增量**:SHA1 不变则缓存命中直接复用;内容变化才重生成。映射表在内存 `Sha1Store` / `StubMergerStore` 中多遍收敛构建,`temp/_sha1_map.txt` 仅供人读。 - 旧产物清理:`--clear-cache`(只清 temp)/ `--rebuild-includes`(只清 includes 预编译缓存)。 ## 工程配置 `project.vpj` 工程使用 `project.vpj`(JSON)管理编译与链接参数。 ```json { "name": "Kernel", "version": "1.0.0", "source_dir": "./Kernel", "build_dir": "./__vpcbuild__", "compiler": { "cmd": "llc", "flags": ["-filetype=obj", "-mtriple=x86_64-none-elf", "-relocation-model=static", "-O2"] }, "linker": { "cmd": "ld.lld.exe", "flags": ["-m", "elf_x86_64", "-T", "linker.ld", "--oformat", "elf64-x86-64", "--strip-all"], "static_libs": [], "dynamic_libs": [], "output": "kernel.bin" }, "includes": ["../../includes"], "target": { "triple": "x86_64-none-elf", "datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" }, "options": { "slice_level": 3, "sha1_slice_level": 1, "target": "llvm", "strict_mode": true } } ``` ### 配置项说明 | 字段 | 类型 | 说明 | |------|------|------| | `name` / `version` | string | 项目名 / 版本 | | `source_dir` | string | 源代码目录(相对 `project.vpj` 所在目录) | | `build_dir` | string | 构建目录(默认 `./__vpcbuild__`);`temp` / `output` 均由它派生 | | `compiler.cmd` / `compiler.flags` | string / string[] | 编译器命令(通常 `llc`)与标志 | | `linker.cmd` / `linker.flags` | string / string[] | 链接器命令与标志 | | `linker.static_libs` | string[] | 静态库 / 目标文件(`.a` / `.obj`) | | `linker.dynamic_libs` | string[] | 动态库(`.dll` / `.so`) | | `linker.output` | string | 输出文件名 | | `includes` | string[] | 模块搜索路径列表(多目录按序查找) | | `target.triple` | string | LLVM 目标三元组 | | `target.datalayout` | string | LLVM 数据布局字符串 | | `options.slice_level` | int | 切片优化级别(见下表) | | `options.sha1_slice_level` | int | SHA1 切片存储级别(默认 1;≥1 时产物按 `{temp}/{sha1[0:2]}/` 存放) | | `options.target` | string | 编译目标(目前仅 `llvm`) | | `options.strict_mode` | bool | 严格模式 | **无统一默认 triple**:由各工程自行指定(ViperC 自身为 `x86_64-pc-windows-gnu`;ViperOS 内核为 `x86_64-none-elf`)。`target.datalayout` 与 `target.triple` 会写入 `.ll` 模块头(`target triple` / `target datalayout`)。 常用 `llc` 标志:`-filetype=obj`(出目标文件)、`-mtriple=...`、`-relocation-model=pic|static`、`-O0..3`。 常用 `ld.lld` 标志:`-m elf_x86_64`、`-T linker.ld`、`--oformat elf64-x86-64|binary`(内核用 binary)、`--strip-all`。 ### 切片优化级别 | 级别 | 名称 | 说明 | |------|------|------| | `0` | `no_optimize` | 无优化 | | `1` | `conservative` | 保守优化 | | `2` | `moderate` | 适度优化 | | `3` | `aggressive` | 激进优化(默认) | | `s` | `size` | 体积优化 | ### 项目结构与产物布局 ``` HelloWorld/ ├── project.vpj # 项目配置 ├── main.vp # 入口 └── __vpcbuild__/ # 构建目录(build_dir,自动生成) ├── temp/ # 临时文件 │ └── / # SHA1 切片子目录(sha1_slice_level ≥ 1) │ ├── .vpi # 签名存根 │ ├── .stub.ll # LLVM IR 声明 │ ├── .text.ll # 含代码的 .ll │ └── .deps.txt # 依赖信息 └── output/ # 输出文件 └── helloworld.exe ``` 内核类工程另含 `linker.ld` 链接脚本(`ENTRY` + `.text/.rodata/.data/.bss` 段控),由工程自行维护。 ## 编译命令 ### TPC(Python 种子编译器) ```bash python TransPyC.py -f InputFile -o OutputFile [options] ``` | 参数 | 说明 | |------|------| | `-f` | 输入文件路径 | | `-o` | 输出文件路径 | | `-wh` | 头文件列表 | | `-debug` | 调试输出文件 | | `-cc` / `-cflags` | 编译命令 / 编译标志 | | `-run` / `-args` | 编译后运行 / 运行参数 | | `-h` | 辅助文件(C 或 Python) | ### VPC(自举产物) ```bash # 工程模式:按 project.vpj 构建 viperc.exe --project project.vpj [--clean] [--run] # viperbuild 模式:入口文件自带构建配置(无需 project.vpj) viperc.exe --src App/main.vp [--run] ``` 常用开关(完整清单见 [ViperC/README.md](../ViperC/README.md)): | 开关 | 说明 | |------|------| | `--project ` | `project.vpj` 路径(不给则在当前目录、上级目录查找) | | `--src ` | 入口文件;构建配置由依赖闭包内的 `viperbuild` 提供(与 `--project` 互斥) | | `--clean` | 清理 temp / output / includes 预编译缓存 | | `--rebuild-includes` | 只删 includes 预编译缓存,强制重编 includes | | `--clear-cache` | 只清 temp 缓存(`.vpi` / `.stub.ll` / `.text.ll`) | | `--phase 1\|2\|all` | 1 = 只做 stub 分离;2 = 只做 IR 组合 + 编译;all = 全部(默认) | | `--run` | 编译成功后立即运行产物 | | `--asan` | ASan 模式(`opt -passes=asan` 插桩;运行库须由 `linker.static_libs` 声明,否则 fail-fast) | | `--traceback` | 生成栈回溯支持(DWARF 元数据 + `.stackmap`),见 [16-traceback.md](16-traceback.md) | | `--detection` / `--detection-deep` | MemBuddy 分配检测(`0xCDCD` 标记 + 探针) | | `--no-llvmpeephole` | 关闭 FunctionPrint 的本地窥孔(T2 store/load 转发、T3 空 bitcast) | | `--repl` | 启动交互式 REPL,见 [15-repl.md](15-repl.md) | | `--nowarn-register` | 抑制 Phase A 依赖类 WARN | | `--version` / `--help` | 版本 / 帮助 | ## viperbuild —— 用 Viper 写构建配置(元编程) `viperbuild` 是**编译器内建的编译期宏库**(与 `t` / `c` / `ctraits` 同级),它让构建配置直接写在 Viper 源码里,免去额外维护 `project.vpj` 的负担。它**不产生任何运行时代码**。 ### 定位与边界 | 扩展名 | 是什么 | 编译器 | Vipeye(LSP) | |--------|--------|--------|---------------| | `.vp` | 可执行 Viper 源码 | 编译 | 推导 | | `.vpi` | 模块签名存根(编译器自产自销) | 读取(跨模块类型主通道) | 推导 | | `.vpnc` | **不可执行的 `.vp`** | **完全无视** | 推导 | | `.vpnci` | **不可执行的 `.vpi`** | **完全无视** | 推导 | `.vpnc` / `.vpnci` 是「Vipeye(基于 pyright 的 VSCode Viper LSP)专用、编译器零感知」的松散 Viper DSL,**不是**为 `viperbuild` 专设。 `viperbuild` 本身也**不是** `.vpnc`:它没有磁盘模块与 `.vpi`,是编译器直接拦截的内建名(`_check_import_sha1` 对 `viperbuild` 豁免)。仓库里的 [includes/viperbuild.vpnci](../includes/viperbuild.vpnci) 只是给 Vipeye 看的存根。 ### 使用方法 配置写在**入口文件依赖闭包内**的任一 `.vp` 模块里(不必在入口文件本身): ```python import viperbuild as vb COMMON_FLAGS = ["-O0", "-g"] proj = vb.Project("ViperC", "1.0.0") # 也支持 Project(name=..., version=...) proj.source = "./App" # 缺省 = 入口文件所在目录 proj.buildroot = "./__vpcbuild__" # 缺省 = source + "/__vpcbuild__" proj.includes = ["../includes"] # 缺省 = 从 exe 目录向上 0..3 层探测 includes proj.compiler = ["llc", "-filetype=obj", "-relocation-model=pic", *COMMON_FLAGS] proj.linker = ["clang++", "-fuse-ld=lld", "-lmsvcrt", "-lucrt", "-g"] proj.static_libs = [".../libclang_rt.asan_dynamic-x86_64.dll.a"] proj.dynamic_libs = [] proj.output = "viperc.exe" # 缺省 = 入口文件名 + ".exe" proj.build(triple="x86_64-pc-windows-gnu", datalayout="e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128") ``` 也可以批量赋值: ```python proj.config(source="./App", buildroot="./__vpcbuild__", includes=["../includes"], ...) ``` `build()` 的目标平台三种写法(**互斥**): - `build(triple=..., datalayout=...)`:显式指定,**必须成对给出**; - `build(selfmachine=True)`:运行时探测本机架构(当前支持 AMD64 → `x86_64-pc-windows-gnu` + 对应 datalayout); - `build()`:什么都不给,等价 `selfmachine=True`。 `selfmachine=True` 与显式 `triple`/`datalayout` **同时给 ⇒ fatal**。 ### 支持的常量表达式 字段右值只接受常量表达式: - 字符串字面量;同文件先定义的字符串常量 / `define` 常量(引用); - 字符串 `+` 拼接; - `list[str]` 字面量,支持 `*名字` 展开(引用同文件的列表常量); - 列表之间 `+` 拼接。 ### 约束与 fail-fast - 一个依赖闭包内**只允许一个** Project 对象,出现第二个即 fatal。 - Project 是**宏类型**:只允许「字段赋值 / `config()` / `build()`」,**禁止读取**(当右值用即 fatal)。 - `source` 缺省为入口文件所在目录;显式指定时,**入口文件必须位于其下**,否则 fatal。 - 没找到 `import viperbuild`、配置损坏(类型不符 / 未知字段 / 用了未定义常量)、`build()` 缺失或重复调用、`--project` 与 `--src` 同时给 —— 一律 fatal,**不静默兜底**。 ### `--src` 的语义 `--src <入口文件>` 时: 1. **Pass 0**:从入口文件出发 BFS 展开**依赖闭包**(搜索根 = 入口目录 + 当前目录;stdlib 模块跳过),识别 `import viperbuild` 并提取配置,写回编译器内部配置; 2. 随后按**工程模式**构建:扫描 `source_dir` 下的 `.vp` 一起编译链接(**没有**「只编一个文件」的独立单文件模式)。 翻译期 `viperbuild` 的宏语句(`proj = vb.Project(...)` / `proj.<字段> = ...` / `proj.config()` / `proj.build()`)会由翻译器**整体擦除**,不生成任何 IR;`import viperbuild` 本身也只记账不产码。 > Pass 0 的实现位于 [ViperC/App/lib/core/builtin/ViperBuild.vp](../ViperC/App/lib/core/builtin/ViperBuild.vp)。 ## 平台说明 当前工具链与运行时验证以 **Windows** 为主(`llc` + mingw `clang++`),原因是计划与精力有限,并非语言 / 编译器设计上的限制;实际大量代码可跨平台复用,后续按需扩展。