3. LLVM 核心架构:前端、中间表示、后端三层架构

LLVM 的架构,说白了就是「三明治」——前端、中间层、后端。我刚开始接触 LLVM 时,觉得这分层有点多余。后来做跨平台编译,才明白这设计有多巧妙。

3.1 三层架构:各司其职

LLVM 把编译过程拆成三个独立阶段:

  • 前端(Frontend):负责解析源代码,生成中间表示。比如 clang 就是 C/C++ 的前端。
  • 中间表示(IR):LLVM 的核心语言,独立于源语言和目标机器。
  • 后端(Backend):把 IR 翻译成目标机器码。

为什么要这么分?你想想看,如果我想支持一种新语言,只需要写个前端,把代码转成 IR 就行。后端完全不用动。反过来,要支持新硬件,也只需要写个后端。

核心思想:前端 + 后端 = N × M 种组合。没有 IR,就得写 N × M 个编译器。

我在项目中遇到过这种情况:团队要同时支持 ARM 和 x86 平台。我们只写了一个前端,IR 层做优化,后端分别生成两种架构的代码。开发量直接减半。

3.2 LLVM IR:编译器的「通用语言」

IR 是 LLVM 的灵魂。它有三种形式:

  • 内存表示:编译过程中在内存里操作的数据结构
  • 比特码(.bc):二进制格式,适合存储和传输
  • 可读文本(.ll):人类可读的汇编风格代码

我个人习惯用 .ll 文件调试,因为能直接看到 IR 指令。举个例子:

; 一个简单的加法函数
define i32 @add(i32 %a, i32 %b) {
  %result = add i32 %a, %b
  ret i32 %result
}

这段 IR 定义了一个函数,接受两个 32 位整数,返回它们的和。你看,它不依赖任何高级语言特性,也不依赖任何 CPU 架构。

小技巧:用 clang -emit-llvm -S test.c 就能把 C 代码转成 .ll 文件。我经常用这招分析编译器做了什么优化。

3.3 LLVM Pass 机制:优化的「插件系统」

Pass 是 LLVM 里最让我着迷的设计。说白了,Pass 就是一个「优化插件」。每个 Pass 只做一件事,比如:

  • 死代码消除:删掉永远不会执行的代码
  • 常量传播:把常量表达式直接算好
  • 循环展开:减少循环开销

Pass 分两种:

类型 特点 例子
FunctionPass 每次处理一个函数 内联优化
ModulePass 处理整个模块 全局变量优化

我曾经踩过一个坑:写了一个 FunctionPass,却想访问其他函数的变量。结果跑起来全是段错误。后来才明白,FunctionPass 只能看到当前函数。要跨函数操作,得用 ModulePass。

避坑指南:Pass 的执行顺序很重要。我曾经把「死代码消除」放在「常量传播」前面,结果很多死代码没被识别出来。正确的顺序是先做常量传播,再做死代码消除。

3.4 LLVM 工具链:三剑客

LLVM 提供了三个核心工具,我几乎每天都在用:

  • clang:前端编译器。把 C/C++ 代码转成 IR 或目标文件。
  • opt:IR 优化器。运行 Pass 对 IR 做优化。
  • llc:后端编译器。把 IR 转成汇编或机器码。

它们的协作流程是这样的:

# 1. 用 clang 生成 IR
clang -emit-llvm -S test.c -o test.ll

# 2. 用 opt 做优化
opt -passes=mem2reg,instcombine test.ll -o test_opt.ll

# 3. 用 llc 生成汇编
llc test_opt.ll -o test.s

我个人习惯把这三步分开做。为什么?因为可以随时查看中间结果。如果最终代码有问题,我能快速定位是前端、优化还是后端出的问题。

经验之谈:调试时多用 opt -print-after-all。这个选项会打印每个 Pass 执行后的 IR。我曾经靠它找到了一个 Pass 把变量优化没了的问题。

3.5 知识体系总览

下面这张图展示了 LLVM 核心架构的全貌。我建议你把它存下来,以后看代码时对照着看。

LLVM 核心架构总览 前端(Frontend) clang(C/C++) | 其他语言前端 词法分析 → 语法分析 → 语义分析 → 生成 IR 中间表示(IR) .ll(文本) | .bc(比特码) | 内存表示 Pass 优化:死代码消除、常量传播、循环优化... 后端(Backend) 指令选择 → 寄存器分配 → 指令调度 → 机器码生成 支持 x86、ARM、RISC-V、WebAssembly 等 工具链:clang → opt → llc clang opt llc

嗯,这张图把三层架构和工具链的关系都串起来了。你写代码时,可以想想当前工作在哪个层,该用哪个工具。

个人建议:初学者先别急着写 Pass。先用 clang 和 llc 把 C 代码编译成不同平台的汇编,感受一下 IR 的「跨平台」特性。等熟悉了 IR 的语法,再动手写 Pass。

LLVM 的三层架构,说白了就是「一次编写,到处运行」的编译器实现。前端只管生成 IR,后端只管翻译 IR,中间层做优化。这种解耦设计,让 LLVM 成了编译器领域的「Linux」——几乎所有人都用它做基础平台。

可可精品资料库