12 KiB
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决定)
- Phase A-pre-vpi / Phase A-pre:加载依赖
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)管理编译与链接参数。
{
"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 种子编译器)
python TransPyC.py -f InputFile -o OutputFile [options]
| 参数 | 说明 |
|---|---|
-f |
输入文件路径 |
-o |
输出文件路径 |
-wh |
头文件列表 |
-debug |
调试输出文件 |
-cc / -cflags |
编译命令 / 编译标志 |
-run / -args |
编译后运行 / 运行参数 |
-h |
辅助文件(C 或 Python) |
VPC(自举产物)
# 工程模式:按 project.vpj 构建
viperc.exe --project project.vpj [--clean] [--run]
# viperbuild 模式:入口文件自带构建配置(无需 project.vpj)
viperc.exe --src App/main.vp [--run]
常用开关(完整清单见 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 |
--detection / --detection-deep |
MemBuddy 分配检测(0xCDCD 标记 + 探针) |
--no-llvmpeephole |
关闭 FunctionPrint 的本地窥孔(T2 store/load 转发、T3 空 bitcast) |
--repl |
启动交互式 REPL,见 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 只是给 Vipeye 看的存根。
使用方法
配置写在入口文件依赖闭包内的任一 .vp 模块里(不必在入口文件本身):
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")
也可以批量赋值:
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 <入口文件> 时:
- Pass 0:从入口文件出发 BFS 展开依赖闭包(搜索根 = 入口目录 + 当前目录;stdlib 模块跳过),识别
import viperbuild并提取配置,写回编译器内部配置; - 随后按工程模式构建:扫描
source_dir下的.vp一起编译链接(没有「只编一个文件」的独立单文件模式)。
翻译期 viperbuild 的宏语句(proj = vb.Project(...) / proj.<字段> = ... / proj.config() / proj.build())会由翻译器整体擦除,不生成任何 IR;import viperbuild 本身也只记账不产码。
Pass 0 的实现位于 ViperC/App/lib/core/builtin/ViperBuild.vp。
平台说明
当前工具链与运行时验证以 Windows 为主(llc + mingw clang++),原因是计划与精力有限,并非语言 / 编译器设计上的限制;实际大量代码可跨平台复用,后续按需扩展。