LLVM 开发指南 / 第 1 章:LLVM 概述与设计哲学
第 1 章:LLVM 概述与设计哲学
“LLVM 不再是一个缩写,它就是项目的名称。” — Chris Lattner
1.1 LLVM 的历史
1.1.1 起源:UIUC 的研究项目
LLVM 最初是 Low Level Virtual Machine 的缩写,由 Chris Lattner 于 2000 年在伊利诺伊大学厄巴纳-香槟分校(UIUC)开始开发,作为其硕士论文项目。
| 时间线 | 里程碑 |
|---|---|
| 2000 年 | Chris Lattner 在 UIUC 开始 LLVM 项目 |
| 2003 年 | LLVM 发布首个版本,获得 USENIX 论文奖 |
| 2005 年 | Apple 聘请 Chris Lattner,LLVM 成为 Apple 官方编译器基础设施 |
| 2007 年 | Clang 项目启动,作为 LLVM 的 C/C++ 前端 |
| 2012 年 | LLVM 3.1 发布,首次完整支持 C++11 |
| 2013 年 | LLVM 成为 Android NDK 的默认编译器 |
| 2016 年 | MLIR 前身概念提出,开始研究多级 IR |
| 2019 年 | MLIR 正式并入 LLVM 项目 |
| 2020 年 | LLVM 11 引入新 PassManager 作为默认 |
| 2023 年 | LLVM 17/18 引入重大 API 变更 |
| 2024 年 | LLVM 18/19 发布,MLIR 生态成熟 |
1.1.2 为什么叫 LLVM?
LLVM 最初确实代表 “Low Level Virtual Machine”,但随着项目的发展,它的应用范围远远超出了虚拟机的概念。如今,LLVM 就是项目的名称本身,不再是一个首字母缩写。
早期: LLVM = Low Level Virtual Machine
现在: LLVM = LLVM(就是名字本身)
注意: 在官方文档和邮件列表中,你可能会看到 “The LLVM Project” 的说法。社区明确表示 LLVM 不再代表任何缩写。
1.1.3 LLVM 的版本命名
LLVM 采用主版本号.次版本号的命名方式,每半年发布一个主要版本:
# 查看当前 LLVM 版本
llvm-config --version
# 输出示例: 18.1.8
# 版本号解读
# 主版本 (Major): 18 — 每半年递增(3月/9月)
# 次版本 (Minor): 1 — 通常是 0 或 1
# 补丁版本 (Patch): 8 — bug 修复
1.2 设计哲学
LLVM 的设计哲学与 GCC 等传统编译器有根本性的不同,这些不同决定了 LLVM 的架构和生态。
1.2.1 模块化设计
核心理念: 编译器不应该是一个不可分割的单体程序,而应该是一组可以独立使用、独立替换的库。
┌─────────────────────────────────────────────────┐
│ 传统编译器 (如 GCC) │
│ ┌───────────────────────────────────────────┐ │
│ │ 词法分析 → 语法分析 → 优化 → 代码生成 │ │
│ │ (紧密耦合,难以单独使用某一部分) │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ LLVM 模块化设计 │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐│
│ │ Clang │ │优化 Pass│ │Backend │ │ JIT ││
│ │ 前端 │ │ 库 │ │ 库 │ │ 引擎 ││
│ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘│
│ │ │ │ │ │
│ └───────────┴───────────┴───────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ LLVM IR │ │
│ │ (公共接口) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────┘
1.2.2 基于库的设计
每个 LLVM 组件都是一个独立的库(Library),可以被外部工具链接和使用:
| 库名 | 功能 | 典型用途 |
|---|---|---|
libLLVMCore | IR 核心数据结构 | 构建和操作 LLVM IR |
libLLVMAnalysis | 分析 Pass | 别名分析、循环分析等 |
libLLVMTransformUtils | 转换工具 | 内联、向量化等优化 |
libLLVMCodeGen | 代码生成 | IR 到机器码 |
libLLVMMC | 机器码层 | 汇编/反汇编 |
libclang | Clang C API | IDE 集成、代码补全 |
libclangTooling | Clang 工具库 | 代码重构、静态分析 |
1.2.3 多层 IR
LLVM 的核心创新之一是引入了多层中间表示(Intermediate Representation):
源代码 (C/C++/Rust/Swift/...)
│
▼
┌─────────────────┐
│ 高层 IR (AST) │ ← 语言特定的前端
└────────┬────────┘
│
▼
┌─────────────────┐
│ LLVM IR │ ← 语言无关的核心 IR
│ (文本/二进制) │
└────────┬────────┘
│
┌────┴────┐
│ │
▼ ▼
┌────────┐ ┌────────┐
│优化 Pass│ │JIT 执行│
└───┬────┘ └───┬────┘
│ │
▼ ▼
┌─────────────────┐
│ SelectionDAG │ ← 中层表示
└────────┬────────┘
│
▼
┌─────────────────┐
│ MachineIR (MIR) │ ← 低层机器特定表示
└────────┬────────┘
│
▼
┌─────────────────┐
│ MCInst │ ← 汇编/机器码层
└────────┬────────┘
│
▼
目标文件 (.o) / 汇编 (.s) / 机器码
1.2.4 “Well-Defined” 的 IR
LLVM IR 是一个精确定义的中间表示,具有以下特点:
- 类型安全: 严格的类型系统,在编译期捕获错误
- 独立于目标: IR 不包含任何特定目标架构的信息
- 可读性强: 提供人类可读的文本格式(
.ll文件) - 可序列化: 提供高效的二进制格式(
.bc文件)
; LLVM IR 示例 — 简单的加法函数
define i32 @add(i32 %a, i32 %b) {
entry:
%result = add i32 %a, %b
ret i32 %result
}
1.2.5 开放与协作
LLVM 采用 Apache 2.0 许可证(含 LLVM Exception),允许商业使用:
许可证: Apache License 2.0 with LLVM Exception
允许:
✅ 商业使用
✅ 修改和分发
✅ 专利使用
✅ 私有修改
要求:
⚠️ 保留版权声明
⚠️ 标注修改内容
⚠️ 包含 NOTICE 文件
1.3 三阶段编译架构
LLVM 采用经典的三阶段(Three-Phase)架构,这是理解 LLVM 的关键。
1.3.1 经典三阶段
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 前端 │ │ 优化器 │ │ 后端 │
│ Frontend │───►│ Optimizer│───►│ Backend │
│ │ │ │ │ │
│ 源代码 │ │ LLVM IR │ │ 优化后 │
│ ↓ │ │ ↓ │ │ LLVM IR │
│ AST │ │ 多种Pass │ │ ↓ │
│ ↓ │ │ │ │ 机器码 │
│ LLVM IR │ │ 优化后IR │ │ │
└──────────┘ └──────────┘ └──────────┘
1.3.2 每个阶段的职责
前端(Frontend):
- 词法分析(Lexical Analysis)
- 语法分析(Parsing)
- 语义分析(Semantic Analysis)
- 生成 LLVM IR
优化器(Optimizer):
- 一系列独立的优化 Pass(Pass Pipeline)
- 与源语言和目标架构无关
- 可以组合、重排序、禁用
后端(Backend):
- 指令选择(Instruction Selection)
- 寄存器分配(Register Allocation)
- 指令调度(Instruction Scheduling)
- 生成目标机器码
1.3.3 三阶段的优势
传统编译器 (N 前端 × M 后端):
需要 N × M 个编译器
C → x86 C → ARM C → RISC-V
C++ → x86 C++ → ARM C++ → RISC-V
Rust → x86 Rust → ARM Rust → RISC-V
LLVM (N 前端 + M 后端):
只需要 N + M 个组件
C ─┐ ┌─ x86
C++ ┤─ LLVM ├─ ARM
Rust┤ IR ├─ RISC-V
... ┘ └─ ...
数学上的优势:
- 传统方式: N × M 个组件
- LLVM 方式: N + M 个组件
- 当 N=10, M=10 时: 100 vs 20
1.4 LLVM 与 GCC 的对比
| 特性 | LLVM | GCC |
|---|---|---|
| 架构 | 模块化、基于库 | 单体(monolithic) |
| 许可证 | Apache 2.0 + LLVM Exception | GPL v3 |
| IR 设计 | 显式设计的多层 IR | 基于 GIMPLE/RTL |
| 可嵌入性 | 天然支持库调用 | 需要特殊包装 |
| 编译速度 | 通常更快 | 相对较慢 |
| 运行时性能 | 接近或略优 | 成熟且稳定 |
| 错误信息 | Clang 诊断极为优秀 | 传统格式 |
| 社区治理 | LLVM Foundation | GNU/FSF |
| 主要支持者 | Apple, Google, ARM, Intel | Red Hat, SUSE, FSF |
1.4.1 许可证差异的实质影响
LLVM (Apache 2.0):
┌──────────────┐
│ 你的编译器 │ ← 可以闭源
│ 链接 LLVM │
│ 修改 LLVM │
└──────────────┘
GCC (GPL v3):
┌──────────────┐
│ 你的编译器 │ ← 修改后的 GCC 部分必须开源
│ 修改 GCC │
│ 分发 GCC │
└──────────────┘
业务场景: Apple、Google 等公司选择 LLVM 的重要原因之一就是其宽松的许可证,允许在商业产品中集成 LLVM 技术而不必开源自己的修改。
1.4.2 Clang vs GCC 错误信息对比
Clang 的错误诊断:
// test.c
struct Point { int x; int y; };
int main() {
struct Point p;
p.z = 10; // 错误:没有成员 'z'
return 0;
}
test.c:4:7: error: no member named 'z' in 'struct Point'
p.z = 10;
~ ^
test.c:2:20: note: 'Point' declared here
struct Point { int x; int y; };
^
GCC 的错误诊断:
test.c:4:7: error: 'struct Point' has no member named 'z'
4 | p.z = 10;
| ^
1.5 LLVM 项目生态
LLVM 已经发展成为一个庞大的项目生态系统:
┌─────────────────────────────────────────────────────────┐
│ LLVM 项目家族 │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Clang │ │ LLDB │ │compiler-rt│ │ libc++ │ │
│ │ C/C++/ │ │ 调试器 │ │ 运行时库 │ │ C++标准 │ │
│ │ ObjC │ │ │ │ │ │ 库 │ │
│ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ MLIR │ │ LLD │ │ libc │ │libunwind│ │
│ │ 多级IR │ │ 链接器 │ │ C标准库 │ │ 栈展开 │ │
│ │ 框架 │ │ │ │ │ │ │ │
│ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ │
│ │Bolt │ │Sanitizers│ │ Polly │ │
│ │二进制 │ │内存/线程 │ │ 循环 │ │
│ │优化器 │ │检查器 │ │ 优化器 │ │
│ └─────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
1.5.1 各子项目简介
| 项目 | 用途 | 说明 |
|---|---|---|
| Clang | C/C++/ObjC 前端 | 提供优秀的诊断信息 |
| LLD | 链接器 | 高性能链接器,替代 ld |
| LLDB | 调试器 | 基于 LLVM 的现代调试器 |
| compiler-rt | 运行时库 | Sanitizers、builtins |
| libc++ | C++ 标准库 | LLVM 的 C++ 标准库实现 |
| MLIR | 多级 IR 框架 | 领域特定编译器基础设施 |
| Polly | 循环优化 | 多面体模型循环优化 |
| Bolt | 二进制优化 | 基于 profile 的二进制优化 |
| libunwind | 栈展开 | C++ 异常处理支持 |
1.6 谁在使用 LLVM?
LLVM 已经成为事实上的编译器基础设施标准:
| 公司/项目 | 使用方式 |
|---|---|
| Apple | Xcode 的默认编译器(Clang/LLVM) |
| Android NDK、Chrome 构建、MLIR for TensorFlow | |
| ARM | ARM 编译器工具链 |
| Intel | Intel oneAPI 编译器 |
| NVIDIA | CUDA 编译器(nvcc 底层) |
| Sony | PlayStation 开发工具链 |
| Rust | rustc 默认使用 LLVM 后端 |
| Swift | 使用 LLVM 作为编译器后端 |
| Emscripten | C/C++ 到 WebAssembly |
| Julia | 科学计算语言,JIT 使用 LLVM |
| Kotlin/Native | 使用 LLVM 后端 |
1.7 第一个 LLVM 程序
让我们用一个简单的例子来体验 LLVM 的工作流程。
1.7.1 从 C 到 LLVM IR
// add.c
int add(int a, int b) {
return a + b;
}
# 生成 LLVM IR(文本格式)
clang -S -emit-llvm add.c -o add.ll
# 生成 LLVM IR(二进制格式)
clang -c -emit-llvm add.c -o add.bc
# 查看生成的 IR
cat add.ll
生成的 LLVM IR(简化):
; ModuleID = 'add.c'
source_filename = "add.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @add(i32 %a, i32 %b) #0 {
entry:
%a.addr = alloca i32, align 4
%b.addr = alloca i32, align 4
store i32 %a, ptr %a.addr, align 4
store i32 %b, ptr %b.addr, align 4
%0 = load i32, ptr %a.addr, align 4
%1 = load i32, ptr %b.addr, align 4
%add = add nsw i32 %0, %1
ret i32 %add
}
attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" ... }
1.7.2 使用 LLVM 工具链
# 完整编译流程
clang add.c -o add # 直接编译
# 分步编译
clang -S -emit-llvm add.c -o add.ll # 步骤1: 生成 IR
llvm-as add.ll -o add.bc # 步骤2: IR 汇编(文本→二进制)
llc add.bc -o add.s # 步骤3: 生成汇编
gcc add.s -o add # 步骤4: 汇编+链接
# 使用 opt 查看优化效果
opt -O2 add.ll -o add_opt.bc # 应用 O2 优化
opt -S -O2 add.ll -o add_opt.ll # 输出优化后的文本 IR
# 使用 llvm-dis 反汇编
llvm-dis add.bc -o add_dis.ll # 二进制 IR → 文本 IR
1.7.3 LLVM 工具链全景
源代码 (.c/.cpp)
│
▼
┌──────────┐
│ clang │ ← 前端
└────┬─────┘
│
┌────▼─────┐
│ LLVM IR │ ← .ll (文本) / .bc (二进制)
└────┬─────┘
│
┌────▼─────┐
│ opt │ ← 优化器
└────┬─────┘
│
┌────▼─────┐
│ llc │ ← 后端代码生成
└────┬─────┘
│
┌────▼─────┐
│ lld │ ← 链接器
└────┬─────┘
│
可执行文件
1.8 本章小结
| 概念 | 要点 |
|---|---|
| LLVM 历史 | 2000 年 UIUC 起源,Apple 主导发展 |
| 设计哲学 | 模块化、基于库、多层 IR、开放许可 |
| 三阶段架构 | 前端 → 优化器 → 后端,N+M vs N×M |
| IR 特点 | 类型安全、语言无关、可读可序列化 |
| 许可证 | Apache 2.0,允许商业使用 |
| 生态 | Clang/LLD/LLDB/MLIR/compiler-rt 等 |
扩展阅读
- LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation — Chris Lattner 的原始论文
- LLVM Architecture Overview — 官方架构文档
- GCC vs LLVM — Wikipedia 对比
- 《Engineering a Compiler》 — Cooper & Torczon,编译器设计经典教材
- Chris Lattner’s Homepage — LLVM 创始人主页
下一章: 第 2 章:安装与环境搭建 — 学习如何从源码编译 LLVM,或通过包管理器快速安装。