253 lines
12 KiB
Markdown
253 lines
12 KiB
Markdown
# 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++`),原因是计划与精力有限,并非语言 / 编译器设计上的限制;实际大量代码可跨平台复用,后续按需扩展。
|