Table of Contents

LLVM

Low Level Virtual Machine

  1. LLVM编译器基础设施 https://llvm.gnu.ac.cn/

[存疑]可以理解为是一个编译器的模板【后日补充:说法不太准确】

Clang是基于LLVM的编译器(是llvm项目的一个子项目)【后日补充:clang只是前端,不是完整编译器】

理解LLVM和编译器

gcc 是编译器,输入源代码(文本),输出可执行程序(二进制、机器指令)。那么这中间发生了什么呢?整体来说肯定是很复杂,但简单来说 gcc 首先解析(或者说理解)代码的内容,将其转变为某种抽象表示(比如树或图、AST抽象语法树、中间层表示IR),然后将这个「抽象表示」其转为机器指令。

语法解析这一步,每种语言都是不同的(因为语法不同嘛),但是从「抽象表示、IR」到机器语言这一步,是比较类似的,可以模板化的,LLVM做的就是这一步。所以LLVM本身也被称为「后端」,那么对应的前端是什么呢?也就是各种语言的语法解析器,对应的C家族的语法解析器就是clang。也就是说clang(前端,语法解析)+LLVM(后端,优化,机器指令生成)就可以构成一个基本的编译器了。

而我们通过修改前端语法解析的部分,也就可以「比较容易」地创建一门新的语言,或者拓展现有语言的语法。比如CUDA基本上是基于C++的,但是拓展了C++的语法,,而且不是用宏等形式,而是真正有原生C++之外的操作符,比如<<<>>>,据说CUDA的编译器nvcc一部分就是使用的LLVM后端。

那么链接器呢?其实链接器(应该)不影响性能(或者影响不大?),而且基本上链接器(linker)是通用的,只要它支持目标平台的二进制格式(比如 ELF for Linux, Mach-O for macOS, PE for Windows等)。所以clang + llvm的编译结果可以使用GUN的链接器(ld)进行链接。当然,链接器不影响性能但是链接器本身有性能好坏,比如链接的速度和内存占用,而llvm项目也包含一个新的链接器lld。

关于链接器的部分,还有一些想补充的:GUN的链接器是ld,LLVM的链接器是lld(gold是一个更老的链接器),两者(基本上)可以通用,但是可能很少见到有人直接调用链接器。其实直接调用链接器是可行的,但是需要自己加上C的很多标准库,指定入口等,比较麻烦。所以我们一般调用gcc a.o b.o c.a这样的命令,其实这不是编译,而是请编译器帮我们加上需要链接的标准库后,再使用链接器完成链接。所以gcc其实是接受「指定链接器」的参数的(-fuse-ld=lld)。

另外,在链接的时候,可能会看到一个叫作collect2的程序,它是什么呢?ld 是 GNU 链接器,是实际执行链接过程的工具;collect2 是 GCC 的辅助程序,在调用 ld 之前和之后执行一些额外操作,它并不真正进行链接,而是调用 ld 来完成链接任务。当使用 g++ 或 gcc 编译程序时,实际上发生的流程可能是:g++ → collect2 → ld。总之collect2是「链接前的辅助程序」。

调试器呢?llvm项目也包含一个叫作lldb的调试器。

LLVM项目的组织方式

代码地址:https://github.com/llvm/llvm-project(所有的子项目都放在一个repo下面)

llvm-project(LLVM项目)包含以下子项目:

  1. llvm(llvm本身,核心项目,后端)[必需]
  2. clang(语法解析器,前端)
  3. lld(链接器)
  4. lldb(调试器)
  5. …(其他子项目)

llvm是项目核心,也是必需,其他所有子项目都依赖于llvm。

如何编译LLVM

首先,LLVM工程包含很多子项目,我们可以选择编译哪些(除了llvm本身是必需)。llvm是其他所子项目的基础,所以llvm必需编译,其他可选。也是因为如此,llvm/CMakeLists.txt是CMake的入口(参照CMake的分层),其他的CMakeLists.txt,比如clang/下面的,请不要直接cmake,而是应该从llvm/CMakeLists.txt开始,通过指定cmake参数的方式,指定编译clang(不需要指定llvm,因为是一定会被编译的)。

基础的编译流程如下:(https://llvm.org/docs/GettingStarted.html

# 首先 clone 代码

# cmake
cmake -S llvm -B build -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_PROJECTS='clang;lld'

# make
cmake --build build -- -j8

编译LLVM这种大型项目,同时包含多个子项目 + Debug模式 + 编译线程开的比较多:容易爆内存(参见Makefile

CUDA

CUDA[AI]

clang可以编译CUDA代码!开源伟大!

NVIDIA 的 NVCC(NVIDIA CUDA Compiler)部分使用了 LLVM 后端,但具体情况稍微复杂一些。

简要结论:

更具体的情况:

1. NVCC 的结构(简化):
2. LLVM 的使用:
3. NVIDIA HPC SDK 的 LLVM 化:

总结:

编译器 是否使用 LLVM 说明
NVCC 部分使用 传统编译器,使用 LLVM 后端生成 PTX,但前端不是 LLVM
clang + CUDA 完全基于 LLVM 的 CUDA 支持
NVIDIA HPC SDK 高性能计算编译器,基于 LLVM,支持 CUDA

如果你是在考虑编译 CUDA 程序时是否可以用 Clang 或其他 LLVM 工具链,也可以进一步了解 clang++ --cuda-gpu-arch 等选项。

杂谈

LLVM是Apple投资的,而在Mac上默认情况下, /usr/bin/gcc 并不是真正的 GNU GCC,而是指向 Apple 自家编译的 Clang。(macOS 从 Xcode 4.2(2011 年发布)开始默认移除了 GNU GCC,只保留了 Clang 编译器 )。所以在MacOS上测试,记录编译环境的时候,不要因为用的命令是gcc就写编译器是gcc(除非你真的确定用的是自己安装的gcc)。

安装

Linux

sudo apt install llvm clang llvm-dev # 暂未验证

Mac

brew install llvm

Windows

从官网下载,然后配置路径

验证安装

安装好后可能需要配置PATH才能访问到llvm-config,能访问到就是安装成功了

llvm-config --version

链接llvm

# 使用 llvm-config 可以给出编译 & 链接的参数

llvm-config --cxxflags --ldflags --libs core
# --cxxflags 给出编译参数,比如-Iinclude_path
# --ldflags 给出链接参数,比如-Llink_path
# --libs core 指定需要链接哪些组件,比如core,给出链接需要的链接参数(应该是)
# 常见的组件有:core executionengine mcjit native

LLVM IR

Intermediate Representation, IR

ExecutionEngine

MCJIT 或 Orc

什么是LLVM(AI)

LLVM(Low Level Virtual Machine)是一个强大的编译器架构,支持静态和动态编译语言的前端和后端。它不仅是一种特定的软件产品,而是一个包括编译器、工具链和中间表示(Intermediate Representation,IR)语言的完整系统,旨在优化编译时间、程序运行时间以及空闲时间的代码生成和执行。

LLVM的核心特征包括:

  1. 模块化和可重用性:LLVM提供一个良好定义的中间表示(IR),可以用于不同阶段的编译和优化,从而实现编译器组件的重用。
  2. 支持多种编程语言:通过不同的“前端”支持C、C++、Objective-C、Fortran、Ada、Haskell、Java字节码、Python、Ruby、Swift等多种语言。
  3. 灵活的中间表示:LLVM IR是类型安全、低级、RISC-like的,支持高级的编译技术和精细的优化。
  4. 广泛的优化阶段:LLVM包括许多编译时、链接时、运行时以及闲置时的优化支持。
  5. 跨平台支持:支持生成多种平台上的代码,包括Windows、Linux、Mac OS X以及多种硬件架构。

LLVM的这些特性使得它不仅被用作传统编译器的后端,也被用于其他类型的语言处理工具,例如静态分析工具和即时编译器。此外,LLVM的设计也极大地促进了编译器开发的研究和教育,因为其代码库具有高度的可读性和文档完善。