强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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),可以被外部工具链接和使用:

库名功能典型用途
libLLVMCoreIR 核心数据结构构建和操作 LLVM IR
libLLVMAnalysis分析 Pass别名分析、循环分析等
libLLVMTransformUtils转换工具内联、向量化等优化
libLLVMCodeGen代码生成IR 到机器码
libLLVMMC机器码层汇编/反汇编
libclangClang C APIIDE 集成、代码补全
libclangToolingClang 工具库代码重构、静态分析

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 的对比

特性LLVMGCC
架构模块化、基于库单体(monolithic)
许可证Apache 2.0 + LLVM ExceptionGPL v3
IR 设计显式设计的多层 IR基于 GIMPLE/RTL
可嵌入性天然支持库调用需要特殊包装
编译速度通常更快相对较慢
运行时性能接近或略优成熟且稳定
错误信息Clang 诊断极为优秀传统格式
社区治理LLVM FoundationGNU/FSF
主要支持者Apple, Google, ARM, IntelRed 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 各子项目简介

项目用途说明
ClangC/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 已经成为事实上的编译器基础设施标准:

公司/项目使用方式
AppleXcode 的默认编译器(Clang/LLVM)
GoogleAndroid NDK、Chrome 构建、MLIR for TensorFlow
ARMARM 编译器工具链
IntelIntel oneAPI 编译器
NVIDIACUDA 编译器(nvcc 底层)
SonyPlayStation 开发工具链
Rustrustc 默认使用 LLVM 后端
Swift使用 LLVM 作为编译器后端
EmscriptenC/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 等

扩展阅读

  1. LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation — Chris Lattner 的原始论文
  2. LLVM Architecture Overview — 官方架构文档
  3. GCC vs LLVM — Wikipedia 对比
  4. 《Engineering a Compiler》 — Cooper & Torczon,编译器设计经典教材
  5. Chris Lattner’s Homepage — LLVM 创始人主页

下一章: 第 2 章:安装与环境搭建 — 学习如何从源码编译 LLVM,或通过包管理器快速安装。