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

12 KiB
Raw Blame History

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)管理编译与链接参数。

{
    "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 <入口文件> 时:

  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。

平台说明

当前工具链与运行时验证以 Windows 为主(llc + mingw clang++),原因是计划与精力有限,并非语言 / 编译器设计上的限制;实际大量代码可跨平台复用,后续按需扩展。