声明:使用的LLVM版本为5.0.1,由于网上大多是教程和博客均以低版本为例,故部分目录结构以及命令可能不同,特此说明。
Pass是LLVM至关重要的组成,所谓Pass,个人理解是LLVM中的一个优化或者转换工作单元,通常做分析或者转换的工作,分析类的Pass主要提供信息,转换类的Pass则要修改中间代码,所有的Pass均作用于中间代码(IR),需要继承自Pass类。另外,LLVM提供了很多已经写好的Pass,并且在编译安装过程中已经编译为.so动态链接库供我们使用,由于还没有详细深入研究,个人见解仅供参考。Pass类的官方文档:http://llvm.org/doxygen/classllvm_1_1Pass.html
下面首先说一下如何根据官方文档,运行其简单的Pass示例——LLVMHello,然后再来记录在其他目录下编写并调用自己的第一个Pass(注意:这里说的是Pass,而不是建立LLVM工程):
一.简单的Pass示例——LLVMHello
Pass的编写推荐阅读官方文档(1):http://llvm.org/docs/WritingAnLLVMPass.html
官方文档(1)中的介绍,是以源码目录llvm/lib/Transforms/Hello中的Hello.cpp为例,可以不进行编写,阅读Hello.cpp中的代码,根据文档修改Hello目录下的CMakeLists.txt如下:
add_llvm_loadable_module( LLVMHello Hello.cpp PLUGIN_TOOL opt )
然后再修改Transforms目录下的CMakeLists.txt,向其中加入:
add_subdirectory(Hello)
之后需要重新执行编译安装LLVM(参考LLVM学习之路(一))
注:看上去很麻烦,并且很慢。3.7版本之前是不需要这样的,可以直接在写Pass的文件下make,3.7版本之后则必须这样做
以上步骤之后即可通过opt命令调用该Pass:
opt -load ../llvm的build路径/lib/LLVMHello.so -hello hello.bc
这里的hello.bc与之前提到的Hello.cpp没有任何关系,hello.bc是我们自己在其他目录下写的一个最简单的helloworld程序,通过clang编译成为二进制.bc文件。并在该目录下执行以上命令
得到如下结果:
WARNING: You're attempting to print out a bitcode file. This is inadvisable as it may cause display problems. If you REALLY want to taste LLVM bitcode first-hand, you can force output with the `-f' option. Hello: main
以上就是官方文档(1)提供的Pass示例。当然,我们也可以在Transform目录下新建我们自己的Pass文件夹,然后重复上述步骤来编译并且调用我们自己的Pass,其中关于文件编写的一些注意事项,会在下面的探讨中说明。
二.我的第一个Pass
网上会有个别大牛推荐建立如下目录:
mypass -CMakeLists.txt ...(1) -src -mypass.cpp -CMakeLists.txt ...(2) -build
或者
<project dir>/ | / CMakeLists.txt <pass name>/ | CMakeLists.txt Pass.cpp ...
这个结构出自官方文档(2):http://llvm.org/docs/CMake.html
这是LLVM工程的推荐结构,但是如果只是学习Pass的编写,并非必需。我们可以直接建立一个文件夹来存放我们的Pass和CmakeLists.txt,然后在目录下建立build文件夹来进行编译。
即:
为第一层目录
为第二层目录
#include "llvm/Pass.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; //使用不具名的空间,保持对象的局部性,具有内链特性 namespace { //定义结构体mypass,继承FunctionPass类 struct mypass : public FunctionPass { static char ID; //类似于构造方法 mypass() : FunctionPass(ID) {} //实现虚函数runOnFunction(Function &F),此函数声明在FunctionPass类中 bool runOnFunction(Function &F) override { //errs是LLVM中定义的C++输出流 errs() << "我自己的: "; errs().write_escaped(F.getName()) << '\n'; return false; } }; // 结构体定义结束 } // 不具名空间结束 char mypass::ID = 0; //注册Pass,所有的Pass必须经过注册才能使用 static RegisterPass<mypass> X("my", "我自己的Pass", false /* Only looks at CFG */, false /* Analysis Pass */);
注释写的应该比较清楚了。这其实只是对Hello.cpp的一个简单重现,其中输出部分改为了中文。另外需要注意的是,注册Pass是,括号内的第一个参数,是你将来在opt命令中的命令行参数,即:
opt -load ../build的路径/你在CMakeLists.txt中定义的名称.so 命令行参数[这里应该是 -my] hello.bc
关于CMakeLists.txt的编写,我的CMakeLists.txt内容如下:
add_library(mypass MODULE mypass.cpp) # 使用c++11 target_compile_features(mypass PRIVATE cxx_range_for cxx_auto_type) # 不使用C++ RTTI. set_target_properties(mypass PROPERTIES COMPILE_FLAGS "-fno-rtti" )
比较简单,第一行就是声明你要生成的.so库使用的是哪个.cpp文件,然后MODULE名称为mypass(个人理解);
接下来就是设置默认的编译选项,使用C++11进行编译,不然会出现如下错误:
error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
对于官方文档(2)中提到的其他语句,我都进行了逐一调试,除了:
add_subdirectory(<pass name>)
和
add_llvm_loadable_module(LLVMPassname Pass.cpp )
这两句分别因为没有子目录和找不到add_llvm_loadable_module报错之外,其他的均可成功。
第一个报错是因为目录结构的原因,第二个我使用了add_library代替。贴出来的是我精简之后的(连最低版本限制都被我删了,最好加上),也不知道会不会有什么影响,仅供参考。
完成编写后,可以在命令行进入build目录,执行:
cmake ../ make之后可以在build目录下看到libmypass.so文件(lib是默认加上的,mypass实在CMakeLists.txt中写了的):
调用Pass分析hello.bc:
opt -load llvm-pass/mypass/build/libmypass.so -my hello.bc
至此,我的第一个Pass算是完成了。