LLVM简介
LLVM项目是一个模块化和可重用的编译器和工具链技术集合,最初作为伊利诺伊大学的研究项目,旨在提供现代的、基于SSA的编译策略,支持任意编程语言的静态和动态编译。如今,LLVM已发展为包含多个子项目的总项目,被广泛应用于商业和开源项目以及学术研究。LLVM的代码在“Apache 2.0许可证加LLVM例外”下发布。
主要子项目包括:
- LLVM核心库:提供与源和目标无关的优化器和代码生成支持。
- Clang:一个高效的C/C++/Objective-C编译器,支持快速编译和有用的错误提示。
- LLDB:基于LLVM和Clang的本地调试器。
- libc++和libc++ ABI:高性能的C++标准库实现。
- compiler-rt:低级代码生成器支持库及动态测试工具的运行时库。
- MLIR:用于构建可重用和可扩展编译器基础设施的新方法。
- OpenMP:为Clang中的OpenMP实现提供运行时支持。
- Polly:实现缓存局部性优化和自动并行化、矢量化。
- libclc:实现OpenCL标准库。
- Klee:使用定理证明器进行符号执行以发现程序中的错误。
- LLD:一个新型高效的链接器。
- BOLT:基于执行配置文件的链接后优化器。
LLVM环境搭建与源码编译
本文以Ubuntu 18.04系统为例,需要安装的软件依赖包为:
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig
安装完成依赖包以后,可以去github下载llvm源码,本文下载的llvm项目源码版本为9.0.1:
git clone https://github.com/llvm/llvm-project.git
进入项目目录以后开始编译Debug版本,编译命令为:
sudo apt-get install -y ninja-build # 如果ninja没有安装
cmake -G Ninja ../llvm
生成配置文件以后,使用Ninja编译,-j表示自己的cpu核数
ninja -j2
与编译Debug版本不同,编译Release版本指定了一些编译选项,-DLLVM_INCLUDE_TESTS=OFF表示在构建过程中不包括测试代码,-DLLVM_ENABLE_PROJECTS="clang"包括clang子项目,表示在构建过程中也会构建Clang编译器。编译命令为:
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_PROJECTS="clang" ../llvm
此时可以使用编译的工具构建自己编写的程序,其功能如下:
- 使用clang编译中间文件
clang -emit-llvm -S hello.c -o hello.ll
- lli工具能够执行中间文件hello.ll
lli hello.ll
- llvm-as: 汇编器,将人类可读的LLVM汇编代码转换为LLVM位代码。
llvm-as hello.ll -o hello.bc
- llc: LLVM后端编译器,将LLVM位代码翻译为本机代码的汇编文件。
llc hello.bc -o hello.s
- 最后将本机的汇编文件使用clang编译成可执行的二进制文件
clang hello.s -o hello
官方文档总结
LLVM(低级虚拟机)提供了一套强大的工具,用于编译、调试和优化代码。这些工具由LLVM库构建而成,形成了用户与LLVM交互的主要接口。以下是一些关键工具的介绍:
- bugpoint: 用于调试优化通道或代码生成后端,通过缩小测试用例,找出导致问题的最小通道和指令。
- llvm-ar: 归档程序,生成包含LLVM位代码文件的存档,并可选择带有索引以加速查找。
- llvm-as: 汇编器,将人类可读的LLVM汇编代码转换为LLVM位代码。
- llvm-dis: 反汇编器,将LLVM位代码转换为人类可读的LLVM汇编代码。
- llvm-link: 链接器,将多个LLVM模块链接成一个单一的程序。
- lli: LLVM解释器,可以直接执行LLVM位代码,对于支持的架构,还可以作为即时编译器,提升执行速度。
- llc: LLVM后端编译器,将LLVM位代码翻译为本机代码的汇编文件。
- opt: 读取LLVM位代码,应用一系列转换,并输出生成的位代码,还可以运行特定分析并打印结果。
每个工具都有详细的帮助文档,可以通过tool_name -help
命令获取更多信息。希望这篇介绍能帮助你快速了解和使用LLVM工具集。
LLVM中Pass介绍
首先明白什么是Pass,我们可以看下官方文档介绍:
LLVM Pass 框架是 LLVM 编译器系统的重要组成部分,负责执行编译器的各种转换和优化任务。Pass 是编译器中用于分析和转换代码的模块,通过继承 Pass 类并重写其虚方法来实现特定功能。不同类型的 Pass(如 ModulePass、FunctionPass、LoopPass 等)提供了关于其功能和如何与其他 Pass 结合使用的详细信息。虽然 LLVM 优化管道现在使用新的 Pass 管理器,但代码生成管道仍然使用旧的 Pass 管理器。Pass 框架能够根据 Pass 的类型和约束高效地调度它们运行。
其中常用的Pass为FunctionPass和ModulePass,下面将逐一介绍:
- FunctionPass: FunctionPass 子类具有可预测的局部行为,系统可以对其进行预期。所有的 FunctionPass 都独立地在程序中的每个函数上执行,不受程序中其他函数的影响。FunctionPass 不要求以特定顺序执行,并且不会修改外部函数。
1. doInitialization(Module &) 方法
- 功能:允许执行大部分 FunctionPass 不允许的操作,如添加和移除函数,获取函数指针等。用于执行简单的初始化操作,不依赖于正在处理的函数。
- 调度:该方法的调用不会与其他 Pass 的执行重叠,因此应该非常快速。
- 示例:例如,LowerAllocations Pass 使用 doInitialization 方法获取所需函数的引用,并添加必要的原型到模块中。
2. runOnFunction(Function &F) 方法
- 功能:必须由子类实现,用于执行 Pass 的主要转换或分析工作。
- 返回值:如果函数被修改,则返回 true;否则返回 false。
3. doFinalization(Module &) 方法
- 功能:在 Pass 框架完成对程序中每个函数的 runOnFunction 调用后调用,用于进行最终的清理工作。
我们自定义的Pass位置:~/Code/llvm-project-9.0.1/llvm/lib/Transforms/Hello
,此时我们可以使用Clion打开整个llvm项目。
opt -load '/home/linker/Code/llvm-project-9.0.1/llvm/cmake-build-debug/lib/LLvMHello.so' -hello hello.ll