Low Level Virtual Machine
[存疑]可以理解为是一个编译器的模板【后日补充:说法不太准确】
Clang是基于LLVM的编译器(是llvm项目的一个子项目)【后日补充:clang只是前端,不是完整编译器】
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的调试器。
代码地址:https://github.com/llvm/llvm-project(所有的子项目都放在一个repo下面)
llvm-project(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)
clang可以编译CUDA代码!开源伟大!
NVIDIA 的 NVCC(NVIDIA CUDA Compiler)部分使用了 LLVM 后端,但具体情况稍微复杂一些。
__global__, __device__, __host__ 等 CUDA 关键字。nvptx(用于生成 PTX 代码)。clang++ 的 LLVM 工具链编译器,用于支持 CUDA,称为 clang CUDA。nvc++)更明确地使用 LLVM 作为核心后端。nvc++、nvc 等编译器已经是 完全基于 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)。
sudo apt install llvm clang llvm-dev # 暂未验证
brew install llvm
从官网下载,然后配置路径
安装好后可能需要配置PATH才能访问到llvm-config,能访问到就是安装成功了
llvm-config --version
# 使用 llvm-config 可以给出编译 & 链接的参数 llvm-config --cxxflags --ldflags --libs core # --cxxflags 给出编译参数,比如-Iinclude_path # --ldflags 给出链接参数,比如-Llink_path # --libs core 指定需要链接哪些组件,比如core,给出链接需要的链接参数(应该是) # 常见的组件有:core executionengine mcjit native
Intermediate Representation, IR
MCJIT 或 Orc
LLVM(Low Level Virtual Machine)是一个强大的编译器架构,支持静态和动态编译语言的前端和后端。它不仅是一种特定的软件产品,而是一个包括编译器、工具链和中间表示(Intermediate Representation,IR)语言的完整系统,旨在优化编译时间、程序运行时间以及空闲时间的代码生成和执行。
LLVM的核心特征包括:
LLVM的这些特性使得它不仅被用作传统编译器的后端,也被用于其他类型的语言处理工具,例如静态分析工具和即时编译器。此外,LLVM的设计也极大地促进了编译器开发的研究和教育,因为其代码库具有高度的可读性和文档完善。