https://blog.csdn.net/weixin_38244174/article/details/82705181
最近开始搞LLVM,下面我将从以下五个方面来介绍LLVM。分别是:(1)LLVM是什么?(2)LLVM的组成部分;(3)LLVM+Clang环境搭建;(4)LLVM的运行过程;(5)LLVM Pass的构建运行过程。
(一)LLVM是什么??
LLVM(low level virtual machine)从本质上来说,是一个开源编译器框架,能够提供程序语言的编译期优化、链接优化、在线编译优化、代码生成。LLVM有两个特点:
(1)LLVM有一个特定指令格式的IR语言,我们可以通过书写Pass来对其IR进行优化。
(2)可以作为多种语言的后端,提供与编程语言无关的优化和针对多种CPU的代码生成功能。
(二)LLVM的组成部分:
LLVM主要由Clang前端、IR优化器(Pass)和LLVM后端构成。其功能分别是:
clang前端:将平台相关的源码生成与平台无关的IR(llvm Bitcode)。
IR优化器:主要对IR进行优化。
llvm后端:将优化后的IR转换为与平台相关的汇编代码或者机器码。
2.1 Clang前端:
Clang前端以.c文件为输入,经语法词法分析后解析为抽象语法数,最后通过LLVM内联API变为LLVM IR。其功能为:词法分析器:把输入的程序代码切成token;语法分析器:接收token流解析为AST。
2.2 IR优化器:
LLVM IR包含三种格式:一种是在内存中的编译中间语言;一种是硬盘上存储的二进制中间语言(以.bc结尾),最后一种是可读的中间格式(以.ll结尾)。这三种中间格式是完全相等的。LLVM IR是LLVM优化和进行代码生成的关键。根据可读的IR,我们可以知道再最终生成目标代码之前,我们已经生成了什么样的代码。我们通过Pass来对IR进行相应的优化。
2.3 LLVM后端:
Llvm clang编译器主要是将各平台源代码编译成与平台无关的IR指令集,这将支撑对IR的优化及转换操作,而llvm后端的主要工作是优化IR指令,并将这些与平台无关的IR指令转换成目标设备相关的指令。
由上图所示,LLVM IR进入后端要经过pass优化,指令选择,指令调度,寄存器分配,代码布局优化以及汇编发行等过程。上述各过程都是pass优化的过程,普通(白色)pass可由用户自定义,内置(灰色)pass由一系列小的pass构成,换句话说我们可以对每一个阶段都可以进行不同程度的优化。同时无须为每个目标平台编写重复的代码。
2.3.1 指令选择
指令选择将内存中三地址结构的IR指令转换成设备相关的DAG节点,如上图所示,指令选择的主要过程有:
(1) 构建初始DAG:该过程只是将LLVM IR指令简单的转换成不合法的SelectionDAG,利用SelectDAGISe类调用visit()函数遍历IR创建DAGNode节点
(2)优化 SelectionDAG:识别目标平台支持的元指令
(3)类型合法化:消除不支持的类型,利用TargetLowering接口
(4)优化SelectionDAG:清除类型合法化后的冗余
(5)操作合法化:将操作进行合法化
(6)优化 SelectionDAG:消除效率低下的操作数
(7)转换设备无关的DAG到目标DAG(DAGNode转换为MachineSDNode(目标平台机器指令))
注:机器指令由.td文件描述。Tablegen就是用于记录这些信息的描述性语言。经过tablegen工具批量生成C++源文件。它的好处就是减少我们描述的工作量。Tablegen主要由Class(类)和Definition(定义)组成。其中Class是用于描述构建其它记录的抽象记录,可以理解成模板。描述目标的共同特点以便批量生成记录。Definition是具体的描述实例,不包含任何未定义的变量。
2.3.2 指令调度
对DAG进行拓扑排序转换为线性指令集尽可能的利用指令级的并行操作,提高运行效率.
2.3.3 寄存器分配
由于IR拥有多个虚拟寄存器,因此需要将输入的任意数目的寄存器重新分配为符合硬件要求的有限个数寄存器。
2.3.4 代码发行
输出汇编代码或二进制代码
(三)LLVM+Clang环境搭建(LLVM3.3):
参考了这篇博客:http://www.cnblogs.com/codemood/p/3142848.html
环境:Ubuntu16.04 32位
gcc版本号:5.3.1
Cmake版本号:3.5.1
3.1新建LLVM文件夹:
mkdir LLVM
3.2下载以下五个包:llvm-3.3.src、cfe-3.3.src、clang-tools-extra-3.3.src、compiler-rt-3.3.src、libcxx-3.3.src将其解压至LLVM文件夹下。可以在这个链接或网页找资源。下载链接:https://download.csdn.net/download/weixin_38244174/10667601
3.3然后按下面的步骤组织,这样可以将clang,clang-tools-extra和compiler-rt就可以和llvm一起编译了。
mv cfe-3.3.src clang
mv clang/ llvm-3.3.src/tools/
mv clang-tools-extra-3.3.src extra
mv extra/ llvm-3.3.src/tools/clang/
mv compiler-rt-3.3.src compiler-rt
mv compiler-rt llvm-3.3.src/projects/
3.4 在LLVM文件夹下新建build目录:
mkdir build
cd build
3.5 配置并编译,时间可能比较长,20min以上。
../llvm-3.3.src/configure --enable-optimized --enable-targets=host-only
make -j4
sudo make install
3.6 验证成功与否:
clang -help
若显示这样则成功:
(四)LLVM+Clang环境搭建(LLVM5.0):
最近项目上需要一个东西,结果LLVM3.3由于太老了,没有一些包,故而搭建LLVM5.0环境,发现5.0的坑不比3.3的少啊,特此记录更新下。
1.新建LLVM文件夹。
2.从LLVM官网上页面上下载clang ,llvm,clang-tools-extra-5.0.0.src,compiler-rt-5.0.0.src,libcxx-5.0.0.src。将其解压至LLVM文件夹下。或者在:https://download.csdn.net/download/weixin_38244174/10971406 下载这几个包。
3.执行以下步骤:
mv cfe-5.0.0.src clang
mv clang/ llvm-5.0.0.src/tools/
mv clang-tools-extra-5.0.0.src extra
mv extra/ llvm-5.0.0.src/tools/clang/
mv compiler-rt-5.0.0.src compiler-rt
mv compiler-rt llvm-5.0.0.src/projects/
4.在LLVM下新建build目录,进入build目录下。
5. 执行
cmake ../llvm cmake -DLLVM_TARGETS_TO_BUILD=X86 -DCMAKE_BUILD_TYPE=Release -DLLVM_USE_LINKER=gold
注意事项:
1.../llvm可能会存在问题,直接将../llvm改为LLVM 5.0源码所在位置,直接拖进去。譬如我自己的是 '/home/hzk/LLVM/llvm-5.0.0.src'
2.不要使用 Debug+Asserts ,如果是 cmake 使用 Release 模式,用Debug会出现很多很多奇怪的错误);
3.如果出现:No CMAKE_CXX_COMPILER could be found.这种报错,这是因为没有安装gcc/g++。安装g++即可,安装步骤如下:
(1).gcc:ubuntu下自带gcc编译器。可以通过“gcc -v”命令来查看是否安装。
(2).g++:安装g++编译器,可以通过命令“sudo apt-get install build-essential”实现。执行完后,就完成了gcc,g++,make的安装。build-essential是一整套工具,gcc,libc等等。通过“g++ -v”可以查看g++是否安装成功。
6.执行make -j4进行编译;
注意事项:
1.可能会出现各种错误,不用管,重新执行make -j4编译,但这种配置低的话可能会多次崩溃;
2.推荐使用make直接进行编译。这种一次成功,但时间相对来说比较长一些。
7.最后执行sudo make install进行安装(一定不要忘了)
(五)LLVM运行过程:
(一)理论基础:
1.如上图所示,以c文件为例,首先使用-emit-llvm命令告诉clang前端将.C文件生成llvm的IR(LLVM IR主要有三种格式:一种是在内存中的编译中间语言;一种是硬盘上存储的二进制中间语言(以.bc结尾),最后一种是可读的中间格式(以.ll结尾)。这三种中间格式是完全相等的)。
2.生成可执行文件有两种方式:
a)使用llc命令将IR文件(.bc文件)生成目标文件(.o文件),通过系统链接器链接多个目标文件,生成可执行文件。(针对单个IR)
b)使用llvm-link链接多个IR,使用llc命令将IR文件生成目标文件,之后生成可执行文件。(针对多个IR)
(二)具体实践:
参考链接:https://blog.csdn.net/earbao/article/details/53421319
对源码的编译。
1.创建简单的c语言源码文件test.c
#include
int main() {
printf("hello llvm\n");
return 0;
}
2.编译可执行文件
clang test.c -o test
3.生成LLVM字节码文件
clang -O3 -emit-llvm test.c -c -o test.bc
4.生成LLVM可视化字节码文件(c文件和OpenCL文件)
clang -O3 -emit-llvm test.c -S -o test.ll
clang -c -x cl -emit-llvm -S -cl-std=CL2.0 -Xclang -finclude-default-header my_kernel.cl -o my_kernel.ll
5.运行可执行文件
./test
6.运行字节码文件
lli test.bc
7.反汇编字节码文件
llvm-dis < test.bc | less
8.编译字节码为汇编文件
llc test.bc -o test.s
(六)LLVM Pass的构建运行过程:
参考博客:https://blog.csdn.net/ZCMUCZX/article/details/80856655
该Pass功能是:opt命令行工具会动态的去加载动态链接库,以运行Pass,之后Pass会遍历每一个函数,输出其函数名,但未对IR做任何改动。
步骤如下:
6.1 创建一个FuncBlockCount.cpp文件,内容如下:
#include "llvm/IR/Function.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
//引入llvm命名空间,可以让其实用LLVM当中的函数
using namespace llvm;
//创建一个匿名的命名空间
namespace {
//声明Pass
struct FuncBlockCount : public FunctionPass
{
//声明Pass的标识符,会被LLVM用作识别Pass
static char ID;
//对父类进行初始化
FuncBlockCount() : FunctionPass(ID){}
//其实就是FunctionPass的一个虚函数,这里对它进行了实现。一个FunctionPass的子类要想做一些实际的工作,就必须对这个虚函数进行实现。
bool runOnFunction(Function &F) override {
//errs()是一个LLVM提供的C++输出流,我们可以用它来输出到控制台
errs() << "Function "<
return false;
}
};
}
//初始化Pass ID
char FuncBlockCount::ID = 0;
//需要注册Pass、填写名称和命令行参数
static RegisterPass
6.2 使用以下命令编译so文件
g++ FuncBlockCount.cpp -fPIC -g -Wall -Wextra -std=c++11 `llvm-config --cxxflags ` -shared -o FuncBlockCount.so
6.3 测试文件为(4)中的test.ll文件;
6.4使用命令运行新Pass.
opt -load /home/kyriehe/Desktop/testpass/FuncBlockCount.so -funcblockcount test.ll
6.5若显示下图则说明运行正确。
6.6 将C语言测试代码编译为LLVM IR的形式
clang -O0 -S -emit-llvm test.c -o test.ll
————————————————
版权声明:本文为CSDN博主「勤修」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_38244174/article/details/82705181