使用新PassManager实现自己的Pass

Pass的本质

Pass的本质是一种由LLVM定义的编程接口,其功能是遍历IR,对其进行分析Analyze或者变形Transform,来实现理论上可行的某种编译优化策略。
一个符合LLVM定义的Pass应该满足如下要求

  1. 继承自llvm::Pass 或者llvm::Pass的子类
  2. 实现接口中定义的纯虚方法(入口),往往以runOnXXX(XXX &xxx)进行命名,如ModulePass中的virtual bool runOnModule(Module &M) = 0;
  3. 拥有一个可取任意值的静态成员static char ID=0
  4. 要对Pass进行注册,新旧管理器

LLVM Pass相关组件

llvm::Pass的子类

ModulePass
CallGraphSCCPass
FunctionPass
LoopPass
RegionPass
BasicBlockPass
在使用传统Pass管理器时,我们需要覆盖并实现这些Pass子类中的runXXX方法

对应头文件

llvm/Pass.h

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类名
比如我们的PassNamehello-world,但是ClassName就是HelloWorld
创建Pass的通用写法

1struct ${PassName} : PassInfoMixin<${PassName}>{
2    //class Body
3};

2. 编写Run方法

针对某种特定Pass,我们需要传递方法要操作的对象,比如Function,以及标注出来对应的PassManagerFunctionPassManger
Pass的类内定义这样的方法

1PreservedAnalyses run(${PASSTYPE} &P, ${PASSTYPE}AnalysisManager &AM);
2//${PASSTYPE}可以为Module,Function
3
4PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
  1. run方法的第一个参数指明了,Pass在遍历IR时所处理的IR对象。如果是Module就写成Module &M
  2. run方法的第二个参数指明了要使用的AnalysisManager 很多Pass并不直接使用这个参数,所以不写参数名也可以,可以直接写成FunctionAnalysisManager &.对于Module来说,就写成ModuleAnalysisManager &
  3. run方法返回一个PreservedAnalyses对象

至此,我们的Pass原型已经构建完毕,接下来只需要

注册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

  1. 产生不带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#.. 指定项目根目录
  1. 产生带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支持两种输入方式

  1. 使用文件流模式输入
1opt-14 -load-pass-plugin ./lib/libHelloWorld.so --passes=hello-world < test.ll
  1. 直接给定文件名
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

    0 评论
avatar