使用新PassManager实现自己的Pass
Pass的本质
Pass的本质是一种由LLVM定义的编程接口,其功能是遍历IR,对其进行分析Analyze
或者变形Transform
,来实现理论上可行的某种编译优化策略。
一个符合LLVM定义的Pass
应该满足如下要求
- 继承自
llvm::Pass
或者llvm::Pass
的子类 - 实现接口中定义的纯虚方法(入口),往往以
runOnXXX(XXX &xxx)
进行命名,如ModulePass
中的virtual bool runOnModule(Module &M) = 0;
- 拥有一个可取任意值的静态成员
static char ID=0
- 要对Pass进行注册,新旧管理器
LLVM Pass相关组件
llvm::Pass
的子类
ModulePass
CallGraphSCCPass
FunctionPass
LoopPass
RegionPass
BasicBlockPass
在使用传统Pass管理器时,我们需要覆盖并实现这些Pass子类中的runXXX
方法
对应头文件
Analyses
LLVM AnalysisManager
构建Pass
以[https://github.com/banach-space/llvm-tutor](https://github.com/banach-space/llvm-tutor)
为例
克隆项目
1git clone https://github.com/banach-space/llvm-tutor && cd llvm-tutor
实现LLVM Function Pass Interface
对应项目源码
1struct HelloWorld : PassInfoMixin<HelloWorld> {
2 // Main entry point, takes IR unit to run the pass on (&F) and the
3 // corresponding pass manager (to be queried if need be)
4 PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
5 visitor(F);
6 return PreservedAnalyses::all();//
7 }
8
9 // Without isRequired returning true, this pass will be skipped for functions
10 // decorated with the optnone LLVM attribute. Note that clang -O0 decorates
11 // all functions with optnone.
12 static bool isRequired() { return true; }
13};
1. 继承PassInfoMixin
继承PassinfoMixin
主要是使用其定义好的name()方法PassinfoMixin
的定义位于llvm/IR/PassManager
其具体定义如下:
1template <typename DerivedT> struct PassInfoMixin {
2 /// Gets the name of the pass we are mixed into.
3 static StringRef name() {//获取自定义类的Pass类的类名
4 static_assert(std::is_base_of<PassInfoMixin, DerivedT>::value,
5 "Must pass the derived type as the template argument!");
6 //断言保证一定给出了模板参数,模板写我们自己的类名就可以
7 StringRef Name = getTypeName<DerivedT>();
8 Name.consume_front("llvm::");
9 return Name;
10
11 }
12 void printPipeline(raw_ostream &OS,
13 function_ref<StringRef(StringRef)> MapClassName2PassName) {
14 StringRef ClassName = DerivedT::name();
15 auto PassName = MapClassName2PassName(ClassName);//PassName和ClassName有一个映射关系
16 OS << PassName; //向输出流中传送pass名字的方法
17 }
18};
PassName
指的是我们在注册Pass
时给出的,以及后面opt --passes=
中使用的Pass名称ClassName
指的就是我们CPP源代码中,定义的Pass
类名
比如我们的PassName
为hello-world
,但是ClassName
就是HelloWorld
创建Pass
的通用写法
1struct ${PassName} : PassInfoMixin<${PassName}>{
2 //class Body
3};
2. 编写Run
方法
针对某种特定Pass
,我们需要传递方法要操作的对象,比如Function
,以及标注出来对应的PassManager
如FunctionPassManger
在Pass
的类内定义这样的方法
1PreservedAnalyses run(${PASSTYPE} &P, ${PASSTYPE}AnalysisManager &AM);
2//${PASSTYPE}可以为Module,Function
3
4PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
run
方法的第一个参数指明了,Pass在遍历IR时所处理的IR对象。如果是Module
就写成Module &M
run
方法的第二个参数指明了要使用的AnalysisManager 很多Pass并不直接使用这个参数,所以不写参数名也可以,可以直接写成FunctionAnalysisManager &
.对于Module
来说,就写成ModuleAnalysisManager &
run
方法返回一个PreservedAnalyses对象
注册Pass
1llvm::PassPluginLibraryInfo getHelloWorldPluginInfo() {
2 //直接返回一个PassPluginLibraryInfo对象
3 return {LLVM_PLUGIN_API_VERSION, //要保证和clang版本一致,否则报错
4 "HelloWorld",//Pass名字,可以随便写
5 LLVM_VERSION_STRING,//版本,可以随便写
6 [](PassBuilder &PB) {
7 PB.registerPipelineParsingCallback(
8 [](StringRef Name, FunctionPassManager &FPM,
9 ArrayRef<PassBuilder::PipelineElement>) {
10 if (Name == "${passname_in_opt}") {
11 //这里给定${passname_in_opt}的字符串用于opt的命令行参数
12 //如果写的hello-world,那么就是opt --passes=hello-world
13 //如果写的abcd,那么就是opt --passes=abcd
14 FPM.addPass(HelloWorld());//使用FunctionPassManager插入Pass
15 return true;
16 }
17 return false;
18 });
19 }};//Lamda表达式,设置PassBuilder
20
21}
22
23
24extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
25llvmGetPassPluginInfo() {
26 return getHelloWorldPluginInfo();
27}
llvm::PassPluginLibraryInfo
1struct PassPluginLibraryInfo {
2 /// The API version understood by this plugin, usually \c
3 /// LLVM_PLUGIN_API_VERSION
4 uint32_t APIVersion;
5 /// A meaningful name of the plugin.
6 const char *PluginName;
7 /// The version of the plugin.
8 const char *PluginVersion;
9
10 /// The callback for registering plugin passes with a \c PassBuilder
11 /// instance
12 void (*RegisterPassBuilderCallbacks) (PassBuilder &);//给定一个PassBuilder
13};
14}
llvmGetPassPluginInfo
对于特定的Pass,OPT会调用Pass中的llvmGetPassPluginInfo
函数,这也是我们当前编写的Pass的入口
使用CMAKE Build
- 产生不带Debug信息(不支持使用GDB调试)的
Target
1mkdir build && cd build && cmake \
2-DCMAKE_CXX_COMPILER=clang++-14 \
3-DLT_LLVM_INSTALL_DIR=/usr/lib/llvm-14/ \
4..
5#-DCMAKE_CXX_COMPILER 指定C++文件的编译器
6#-DLT_LLVM_INSTALL_DIR 项目自定义的变量,用于传递LLVM的安装目录
7#.. 指定项目根目录
- 产生带Debug信息的
Target
1mkdir build && cd build && cmake \
2-DCMAKE_CXX_COMPILER=clang++-14 \
3-DLT_LLVM_INSTALL_DIR=/usr/lib/llvm-14/ \
4-DCMAKE_BUILD_TYPE=debug \
5..
使用objdump
查看生成的二进制文件是否携带Debug所需要的附加信息(元数据)
1objdump -h ./lib/libHelloworld.so
可以清楚地看到部分结果携带了debug附加信息,而使用非Debug模式就看不到
125 .comment 0000004b 0000000000000000 0000000000000000 0000d278 2**0
2 CONTENTS, READONLY
3 26 .debug_info 0000ab64 0000000000000000 0000000000000000 0000d2c3 2**0
4 CONTENTS, READONLY, DEBUGGING, OCTETS
5 27 .debug_abbrev 00000e39 0000000000000000 0000000000000000 00017e27 2**0
6 CONTENTS, READONLY, DEBUGGING, OCTETS
7 28 .debug_line 00002d62 0000000000000000 0000000000000000 00018c60 2**0
8 CONTENTS, READONLY, DEBUGGING, OCTETS
9 29 .debug_str 00016c57 0000000000000000 0000000000000000 0001b9c2 2**0
10 CONTENTS, READONLY, DEBUGGING, OCTETS
11 30 .debug_addr 00000858 0000000000000000 0000000000000000 00032619 2**0
12 CONTENTS, READONLY, DEBUGGING, OCTETS
13 31 .debug_line_str 0000074a 0000000000000000 0000000000000000 00032e71 2**0
14 CONTENTS, READONLY, DEBUGGING, OCTETS
15 32 .debug_rnglists 00000371 0000000000000000 0000000000000000 000335bb 2**0
16 CONTENTS, READONLY, DEBUGGING, OCTETS
17 33 .debug_str_offsets 00001de0 0000000000000000 0000000000000000 0003392c 2**0
18 CONTENTS, READONLY, DEBUGGING, OCTETS
使用Opt加载Pass
1opt-14 -load-pass-plugin ./lib/libHelloWorld.so --passes=hello-world
-load-pass-plugin
指明Pass
源代码生成的动态库,当opt加载好这个动态库时,opt就会将该Pass变为已知且可用--passes=hello-world
代表加载名为hello-world
这个pass,由于已经使用了-load-pass-plugin ./lib/libHelloWorld.so
加载了我们的pass,所以现在opt可知hello-world这个pass了,所以就可以成功使用
opt 输入
opt支持两种输入方式
- 使用文件流模式输入
1opt-14 -load-pass-plugin ./lib/libHelloWorld.so --passes=hello-world < test.ll
- 直接给定文件名
1opt-14 -load-pass-plugin ./lib/libHelloWorld.so --passes=hello-world test.ll
测试pass是否生效
准备测试源码test.cpp
1#include <iostream>
2
3using namespace std;
4
5void fun1(int a , int b){
6 cout << a+b;
7}
8void fun2(int c ){
9 cout << c ;
10}
11int main(){
12
13 cout << "helloworld";
14 return 0;
15
16}
使用clang
将其转换为IR
格式,生成文件test.ll
1clang -emit-llvm -S test.cpp
使用opt
应用pass
1opt-14 \
2-load-pass-plugin ./lib/libHelloWorld.so \
3--passes=hello-world \
4-disable-output \
5test.ll
6
7#-disable-output 代表关闭输出,这个输出指的是输出应用pass后的bitcode,
8#但是由于我们的Pass没有对代码进行变形或者修改,所以没必要输出
由于我们的Pass被调用了,Pass里面一些会产生输出的代码会被执行,所以还是有输出的
1(llvm-tutor) Hello from: _Z4fun1ii
2(llvm-tutor) number of arguments: 2
3(llvm-tutor) Hello from: _Z4fun2i
4(llvm-tutor) number of arguments: 1
5(llvm-tutor) Hello from: main
6(llvm-tutor) number of arguments: 0
7(llvm-tutor) Hello from: _GLOBAL__sub_I_test.cpp
8(llvm-tutor) number of arguments: 0
打印的是IR中定义的一些函数
标题:使用新PassManager实现自己的Pass
作者:cyberloafing
地址:https://www.goloaf.top/articles/2022/06/30/1656582821829.html