LLVM介绍

  • LLVM是一个跨平台的编译器基础设施,它不是一个单一的编译器,而是一系列工具和库的集合,其提供了丰富的数据结构和中间表示层,是实现编译原理的最佳框架。
  • LLVM的架构和GCC编译器的架构一样,都是前端、中端、后端三层架构,但是LLVM的编译器的三层架构之间是相互独立的,而不是互相关联的。这就大大减少了编译器的开发工作。具体的LLVM三层架构如下图所示:
    • LLVM前端:LLVM前端的编译器,一般是由语言的开发者或者开发团队进行编写的,而不是LLVM自带的,就好比Clang就是配套LLVMC语言编译器。这些前端的编译器,主要就是将高级语言编译成LLVM IR,这个中间语言。
    • LLVM中端:从LLVM中端开始才是LLVM的一些工具集,并且LLVM中端的主要作用就是将LLVM IR进行优化,使得后端编译和执行的时候变得更高效。
    • LLVM后端:LLVM后端的只要作用就是将LLVM IR,编译成不同架构的机器语言。后端的编译器也是LLVM的工具集。

img

  • 补充1:常见的LLVM前端编译器
语言类型 适配LLVM的编译器
C/C++/Object-C/Object-C++ Clang
Fortran llvm-gcc Frotran
Rust rustc
Swift Swift Compiler
  • 补充2:加入LLVM IR使得编译器编写的工作量减少很多。假设有M种语言,N种硬件:

    • 如果按照gcc三层架构比较紧凑的编译器设计,那么一共就需要开发M×N个编译器。(不讨论交叉编译的情况)
    • 如果按照LLVM三层架构相互独立的编译器设计,那么一共就需要开发M+N个编译器,其中M个是开发的是编译器的前端数量,N个是开发编译器的后端数量。
  • 补充3:LLVM版本介绍与对应版本的一些比较大的改动对比。

版本范围 重大更新内容
LLVM2.0版本 引入了bitcode format
LLVM3.0版本 进行了tpye system rewrite
LLVM4.0版本 版本号体系发生变化
LLVM9.0到LLVM13.0 现代LLVM逐渐成熟
LLVM15.0版本之后 Opaque Pointer默认启用
LLVM17.0版本之后 Typed Pointer正式消失

LLVM环境搭建

  • 通过对LLVM的初步了解,以及对LLVM版本的更新内容的一些了解,现在来搭建LLVM的环境。这里推荐搭建LLVM 17.0以及之后的版本,因为该版本以及后续版本的LLVM IR中的Typed Pointer不会再出现。在LLVM IR中举个例子:
    • LLVM 17.0版本之前,这样的IR语法是被支持的: i32* %1
    • LLVM 17.0版本之后,上面的IR语法统一替换成:ptr %1
    • 其实也就是不显示标明具体的指针指针类型。

Linux下LLVM环境安装

  • 本人只在Linux下安装LLVM,安装过程如下。
  • 首先,先使用一下命令,更新一下软件包:
1
apt update
  • 接着使用apt search命令搜索LLVM的软件包,发现软件包里面最高只有到llvm 15版本的软件包:
1
apt search llvm

image-20260920175212392

  • 那就直接从github上,下载已经编译号的LLVM最新版本
1
wget https://github.com/llvm/llvm-project/releases/download/llvmorg-23.1.1/LLVM-23.1.1-Linux-X64.tar.xz

LLVM IR

  • 本人使用Claude Code配合教师skills,它了解了本人的实际情况后,推荐我从LLVM IR开始学习。
  • 中间表示码是一种抽象的机器指令,它不针对具体的硬件,而是一种通用的指令集。它是一种中间表达,可以被进一步编译为目标代码。
  • LLVM IR是一种中间语言表示,作为编译器前端和后端的分水岭,LLVM编译器的前端Clang负责产生IR,而其LLVM后端负责消费IR
  • IR介于高级语言与汇编语言之间(更偏向汇编语言一些),发明IR是为了统一来自不同源码语言,去往不同目标硬件的一层抽象层。优点如下:
    • 第一个优点是:便于前端统一实现,Clang这样的前端只需要生成抽象的数学运算,控制流这些IR预先定义号的指令就可以了,不用去专门为每个硬件设计一套生成汇编的引擎。
    • 第二个优点是:LLVM IR采用了对优化更友好的SSA格式,而不是糟糕的寄存器格式,大大方便了优化,等送到后端的末尾才开始将IR翻译为汇编代码,最终变成可执行的机器码。
  • 在正式了解LLVM IR之前,先来了解一下要使用到的关于LLVM中的组件和工具。
    • ClangC/C++的前端编译器
    • llc LLVM静态编译器
    • llvm-asLLVM IR汇编器
    • llvm-disLLVM IR反汇编器
    • opt:LLVM中的LLVM IR优化器

LLVM IR文件

  • 先来看看LLVM实体文件具体是怎么样的,以及文件里面具体的内容。

  • LLVM IR有多种表现形式:

    • 内存中的LLVM IR:内存中的IR是一组由C++对象构成的IR对象图,位于clanglibcLLVM.so进程的内存中,都是一个个C++类,通过指针相互连接。由于内存中LLVM IR对象存在虚表指针,以及其复杂的树状或图状数结构,无法直接存入磁盘以及人无法阅读,所以提供了下面两种磁盘中的存储形式。
    • 磁盘中的LLVM IR汇编:该形式通常是以文件.ll形式存储的,以人类可读的文本形式存储在磁盘中,方便人类观察、修改和调试。
    • 磁盘中的LLVM IR字节码:该形式通常是以文件.bc形式存储的,本质上和LLVM IR汇编相同,只是以更高效的二进制字节存储。虽然人类看不懂,但是节约磁盘空间,以及方便程序快速解析。
  • 接下来我们就具体来查看一下这三种形式,下面有一个C语言的函数代码:

1
2
3
4
5
int add(int a, int b) {
int c = a + b;
return c;
}
// exp.c
  • 使用clang前端将该.c文件编译成两种不同形式的LLVM IR文件:
1
2
3
4
# 编译成LLVM IR汇编
clang-17 -emit-llvm -S -o0 sum.c -o my_sum.ll
# 编译成LLVM IR字节码
clang-17 -emit-llvm -c -o0 sum,c -o my_sum.bc

image-20260921160134172

image-20260921160257958

  • 使用LLVM工具集中的汇编器llvm-asLLVM工具集中的反汇编器llvm-dis可以使.ll.bc这两个形式的文件进行互相转换。
1
2
3
4
# .bc文件反汇编成.ll文件
llvm-dis-17 my_sum.bc -o my_from_bc.ll
# .ll文件汇编成.bc文件
llvm-as-17 my_sum.ll -o my_from_ll.bc

image-20260921160602465

image-20260921160643785

当需要长期储存 IR 的中间结果时会用到 IR 字节码,当需要人类观察分析中间结果时就需要 IR 汇编。

LLVM IR特点

  • LLVM IR的四层结构:模块、函数、基本块、指令。
    • 整个LLVM文件的内容会被视为一个LLVM模块模块是LLVM IR顶层数据结构。
    • 每个模块包含一系列函数,每个函数由一系列基本块组成。模块还包含用于支持该模型的外围实体,如全局变量、目标数据布局、外部函数原型以及数据结构声明。
  • LLVM IR具有无限寄存器:
    +

LLVM IR类型

LLVM IR指令

内存指令

控制流指令

终结指令

补充

LLVM项目的起源

LLVM项目总览

  • 在LLVM项目的GitHub仓库中,其实有很多个子项目,我们在补充这里逐一介绍一下。直接从LLVM对应的GitHub仓库中拉取项目。仓库链接如下:https://github.com/llvm/llvm-project.git
1
git clone https://github.com/llvm/llvm-project.git
  • 接着在终端中使用命令,将该项目的文件以目录树的形式在终端中输出出来。
1
tree -L 1
  • 会输出如下树状形式的文件:
    • 我们主要学习:这些文件都是LLVM的子项目,其中我们主要学习的是llvm文件夹,他是LLVM的本体。llvm文件夹不仅包含了LLVM库,也包含一些处理LLVM IR和字节码的实用工具。
    • clang文件夹:这个子项目就是大名鼎鼎的Clang编译器,他也是基于LLVM本体实现的,本身只是个前端,并不做优化和后端汇编的生成。
    • clang-tools-extra:这个子项目是clangd、clang-tidy、clang-formatC/C++代码质量工具,可以选择不构建。
    • cmake文件夹:该文件夹并不是子项目,而是装着和LLVM相关的一些CMake脚本文件。
    • compiler-rt:是AddressSantizer(内存溢出检测工具)、MSAN(内存泄漏检测)、TSAN(线程安全检测)、UBSAN(未定义行为检测)等工具的实现。
    • flang文件夹:是LLVM的Fortan前端,编程界的活化石。
    • libc文件夹:是Clang官配的C标准库
    • libcxx文件夹:是Clang官配的C++标准库
    • lldb文件夹:是LLVM官方的调试器,对标GCC的gdb调试器,VScode的调试默认就是基于lldb
    • libclc文件夹:是LLVM对OpenCL的实现
    • mlir文件夹:是LLVM对MLIR的编译器实现(MLIR是一种为机器学习定制,允许用户自定义新的IR节点,例如矩阵乘法等高阶操作,方便特定硬件识别奥并优化成自研硬件专门的矩阵乘法指令)
    • openmp:是LLVM对OpenMP的实现,一种用于傻瓜式CPU单机并行的框架。
    • pstl:是LLVM对C++17Parallel STL的实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
myheart@aaa /m/e/c/llvm-project-main> tree -L 1
.
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.TXT
├── README.md
├── SECURITY.md
├── bolt
├── clang
├── clang-tools-extra
├── cmake
├── compiler-rt
├── cross-project-tests
├── flang
├── flang-rt
├── libc
├── libclc
├── libcxx
├── libcxxabi
├── libsycl
├── libunwind
├── lld
├── lldb
├── llvm
├── llvm-libgcc
├── mlir
├── offload
├── openmp
├── orc-rt
├── polly
├── pyproject.toml
├── runtimes
├── third-party
└── utils