Files
Viper_Wiki/12-compilation-system.md
T
2026-10-13 15:36:42 +08:00

253 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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]}/` 切片存放 `<SHA1>.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[0:2]>/ # SHA1 切片子目录(sha1_slice_level ≥ 1)
│ ├── <SHA1>.vpi # 签名存根
│ ├── <SHA1>.stub.ll # LLVM IR 声明
│ ├── <SHA1>.text.ll # 含代码的 .ll
│ └── <SHA1>.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 <path>` | `project.vpj` 路径(不给则在当前目录、上级目录查找) |
| `--src <path>` | 入口文件;构建配置由依赖闭包内的 `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 元数据 + `<exe>.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++`),原因是计划与精力有限,并非语言 / 编译器设计上的限制;实际大量代码可跨平台复用,后续按需扩展。