LLVM 之 IR 篇(4):如何基于传统 Pass 框架扩展 LLVM IR 优化器

Author: stormQ

Created: Friday, 30. July 2021 11:24PM

Last Modified: Saturday, 21. August 2021 03:29PM



摘要

本文基于release/12.x版本的 LLVM 源码,通过示例介绍了基于传统 Pass 框架扩展 LLVM IR 优化器的两种不同方式:以插件方式扩展在源码中扩展,以及如何编写带依赖的 Pass。从而,初步了解 LLVM 传统 Pass 框架以便更深入地研究相关内容。


如何以插件方式扩展 LLVM IR 优化器

本节通过实现一个用于统计函数参数个数的 Pass(不依赖其它 Pass),介绍了如何基于传统 Pass 框架(The Legacy Pass Manager)以插件方式扩展 LLVM IR 优化器。

step 1: 实现自定义的 Pass

1) 添加实现代码

llvm/lib/Transforms目录中新建FnArgCnt目录。并在该目录下新建FnArgCntPlugin.cpp文件,内容如下:

  1 #include "llvm/IR/Function.h"
  2 #include "llvm/Pass.h"
  3 #include "llvm/Support/raw_ostream.h"
  4 
  5 using namespace llvm;
  6 
  7 namespace {
  8 
  9 class FnArgCntPluginLegacyPass : public FunctionPass {
 10 public:
 11   FnArgCntPluginLegacyPass() : FunctionPass(ID) {}
 12 
 13   bool runOnFunction(Function &F) override;
 14 
 15   static char ID;
 16 };
 17 
 18 } // anonymous namespace
 19 
 20 char FnArgCntPluginLegacyPass::ID = 0;
 21 
 22 bool FnArgCntPluginLegacyPass::runOnFunction(Function &F) {
 23   errs() << "FnArgCntPluginLegacyPass --- " << F.getName()
 24          << ": " << F.arg_size() << "\n";
 25   return false;
 26 }
 27 
 28 static RegisterPass<FnArgCntPluginLegacyPass> X(
 29     "plugin.fnargcnt""Function Argument Count Plugin Legacy Pass",
 30     falsefalse);

注:

2) 添加 CMakeLists.txt

在目录llvm/lib/Transforms/FnArgCnt/中新建CMakeLists.txt文件。其内容如下:

add_llvm_library(LLVMFnArgCntPlugin MODULE FnArgCntPlugin.cpp PLUGIN_TOOL opt)

注:这里的LLVMFnArgCntPlugin表示要生成的插件名称为libLLVMFnArgCntPlugin.so

step 2: 修改 Transforms 目录中的 CMakeLists.txt

llvm/lib/Transforms/CMakeLists.txt文件中添加如下内容:

add_subdirectory(FnArgCnt)

注:这里的FnArgCnt即为步骤step 1中新建的目录名称。

step 3: 编译插件

如果你已经编译并安装过 Clang,那么在构建目录中执行如下命令:

$ ninja -j8

注:如果你还未编译并安装过 Clang,那么可参考笔者的另一篇文章《LLVM 之 Clang 篇(1):如何从源码构建并安装 Clang》。

编译完成后,在构建目录中执行如下命令查看所生成插件中的 Pass:

$ opt -load ./lib/LLVMFnArgCntPlugin.so --help | grep plugin.fnargcnt
      --plugin.fnargcnt                                                    - Function Argument Count Plugin Legacy Pass

从上面的输出可以看出:

需要注意的是, 插件的路径(这里是./lib/LLVMFnArgCntPlugin.so)可以是相对路径,也可以是绝对路径。

step 4: 运行插件

方式 1:

$ opt -load ~/git-projects/llvm-project/build_ninja/lib/LLVMFnArgCntPlugin.so -plugin.fnargcnt test.bc > /dev/null

方式 2:

$ opt -load ~/git-projects/llvm-project/build_ninja/lib/LLVMFnArgCntPlugin.so -disable-output -plugin.fnargcnt test.bc

注:上述命令中添加> /dev/null重定向或者-disable-output选项都是为了避免以下内容的输出:

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.

执行上述命令后,输出如下:

FnArgCntPluginLegacyPass --- foo: 1

从上面的输出可以看出,我们以插件方式扩展的 Pass——plugin.fnargcnt实现了统计函数参数个数的功能。


如何在源码中扩展 LLVM IR 优化器

本节介绍了如何基于传统 Pass 框架在源码中扩展上一节中的 Pass。

需要注意的是, 这里将该 Pass 的源码文件添加到了llvm/lib/Transforms/Scalar目录中,从而集成到共享库libLLVMScalarOpts.so中。但不是所有基于传统 Pass 框架在源码中扩展的 Pass 都是如此,也可以添加到llvm/lib/Transforms/的其它子目录中(比如llvm/lib/Transforms/IPO),也可以添加到新建的llvm/lib/Transforms/子目录中。源码文件应该添加到哪个目录,取决于 Pass 要实现的功能。

step 1: 实现自定义的 Pass

1) 添加实现代码

llvm/lib/Transforms/Scalar目录中新建FnArgCnt.cpp文件,内容如下:

  1 #include "llvm/InitializePasses.h"
  2 #include "llvm/Transforms/Scalar.h"
  3 #include "llvm/IR/Function.h"
  4 #include "llvm/Pass.h"
  5 #include "llvm/Support/raw_ostream.h"
  6 
  7 using namespace llvm;
  8 
  9 #define DEBUG_TYPE "fnargcnt"
 10 
 11 namespace {
 12 
 13 class FnArgCntLegacyPass : public FunctionPass {
 14 public:
 15   static char ID;
 16 
 17   FnArgCntLegacyPass() : FunctionPass(ID) {
 18     initializeFnArgCntLegacyPassPass(*PassRegistry::getPassRegistry());
 19   }
 20 
 21   bool runOnFunction(Function &F) override {
 22     errs() << "FnArgCntLegacyPass --- " << F.getName()
 23            << ": " << F.arg_size() << "\n";
 24     return false;
 25   }
 26 };
 27 
 28 } // anonymous namespace
 29 
 30 char FnArgCntLegacyPass::ID = 0;
 31 
 32 INITIALIZE_PASS(FnArgCntLegacyPass, DEBUG_TYPE,
 33                 "Function Argument Count Legacy Pass"falsefalse)
 34 
 35 FunctionPass *llvm::createFnArgCntLegacyPass() {
 36   return new FnArgCntLegacyPass();
 37 }

注:

step 2: 修改 InitializePasses.h

llvm/include/llvm/InitializePasses.h文件中添加如下内容:

void initializeFnArgCntLegacyPassPass(PassRegistry&);

修改后的内容为:

namespace llvm {
省略 ...
void initializeFnArgCntLegacyPassPass(PassRegistry&);
// end namespace llvm

需要注意的是, 上述代码用于提供函数initializeXXXPass()的声明。因此,其中的XXX必须为 Pass 的类名(这里为FnArgCntLegacyPass)。

step 3: 修改 LinkAllPasses.h

llvm/include/llvm/LinkAllPasses.h文件中添加如下内容:

(void) llvm::createFnArgCntLegacyPass();

修改后的内容为:

namespace {
  struct ForcePassLinking {
    ForcePassLinking() {
      省略 ...
      (void) llvm::createFnArgCntLegacyPass();
      省略 ...
    }
  } ForcePassLinking; // Force link by creating a global definition.
}

需要注意的是, 上述代码用于调用 Pass 的创建函数。因此,这里的函数createFnArgCntLegacyPass()必须与步骤step 1中的保持一致。

step 4: 修改 Scalar.h

1)llvm/include/llvm/Transforms/Scalar.h文件中添加如下内容

FunctionPass *createFnArgCntLegacyPass();

修改后的内容为:

namespace llvm {
省略 ...
FunctionPass *createFnArgCntLegacyPass();
// End llvm namespace

需要注意的是, 上述代码用于提供 Pass 实例创建函数的声明。因此,这里的函数createFnArgCntLegacyPass()必须与步骤step 1中的保持一致。

2)llvm/include/llvm-c/Transforms/Scalar.h文件中添加如下内容(optional)

/** See llvm::createFnArgCntLegacyPass function. */
void LLVMAddFnArgCntLegacyPass(LLVMPassManagerRef PM);

上述代码在 llvm-c 库中添加一个外部接口LLVMAddFnArgCntLegacyPass()。从而,允许其他编程语言通过调用该 C 接口运行该 Pass。

step 5: 修改 Scalar.cpp

1)llvm/lib/Transforms/Scalar/Scalar.cpp文件中添加如下内容

initializeFnArgCntLegacyPassPass(Registry);

修改后的内容为:

void llvm::initializeScalarOpts(PassRegistry &Registry) {
  省略 ...
  initializeFnArgCntLegacyPassPass(Registry);
}

需要注意的是, 上述代码用于将 Pass 实例添加到共享库libLLVMScalarOpts.so中。因此,这里的函数initializeFnArgCntLegacyPassPass()必须与步骤step 1中的保持一致。

注: 在添加上述代码后,如果去掉FnArgCnt.cpp中的第 18 行,那么测试程序test.bc也可以正常运行并输出相同的结果。

2)llvm/lib/Transforms/Scalar/Scalar.cpp文件中添加如下内容(optional)

void LLVMAddFnArgCntLegacyPass(LLVMPassManagerRef PM{
  unwrap(PM)->add(createFnArgCntLegacyPass());
}

step 6: 修改 Scalar 目录中的 CMakeLists.txt

llvm/lib/Transforms/Scalar/CMakeLists.txt文件中添加如下内容:

FnArgCnt.cpp

注:这里的FnArgCnt.cpp即为步骤step 1中新建的源文件名称。

step 7: 编译安装

$ ninja -j8
$ sudo ninja install

安装完成后,查看默认的opt工具是否包含选项fnargcnt

$ opt --help | grep fnargcnt
      --fnargcnt                                                           - Function Argument Count Legacy Pass

step 8: 运行

$ opt -disable-output -fnargcnt test.bc

执行上述命令后,输出如下:

FnArgCntLegacyPass --- foo: 1

从上面的输出可以看出,我们在源码中扩展的 Pass——fnargcnt也实现了统计函数参数个数的功能。


如何编写带依赖的 Pass

本节介绍了如何实现一个依赖于其他 Pass 的 Pass——统计循环内基本块的数量,并基于传统 Pass 框架以插件方式进行扩展。

step 1: 实现自定义的 Pass

1) 添加实现代码

llvm/lib/Transforms目录中新建FnBlockCnt目录。并在该目录下新建FnBlockCntPlugin.cpp文件,内容如下:

  1 #include "llvm/IR/Function.h"
  2 #include "llvm/Pass.h"
  3 #include "llvm/Support/raw_ostream.h"
  4 #include "llvm/Analysis/LoopInfo.h"
  5 
  6 using namespace llvm;
  7 
  8 namespace {
  9 
 10 class FnBlockCntPluginLegacyPass : public FunctionPass {
 11 public:
 12   FnBlockCntPluginLegacyPass() : FunctionPass(ID) {}
 13 
 14   bool runOnFunction(Function &F) override;
 15 
 16   void getAnalysisUsage(AnalysisUsage &AU) const override;
 17 
 18   static char ID;
 19 
 20 private:
 21   void countBlocksInLoop(Loop *L, unsigned nest);
 22 };
 23 
 24 } // anonymous namespace
 25 
 26 char FnBlockCntPluginLegacyPass::ID = 0;
 27 
 28 bool FnBlockCntPluginLegacyPass::runOnFunction(Function &F) {
 29   errs() << "FnBlockCntPluginLegacyPass --- " << F.getName() << "\n";
 30   LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
 31   for (Loop *L : LI) {
 32     countBlocksInLoop(L, 0);
 33   }
 34   return false;
 35 }
 36 
 37 void FnBlockCntPluginLegacyPass::countBlocksInLoop(Loop *L, unsigned Nest) {
 38   unsigned NumBlocks = 0;
 39   for(auto BB = L->block_begin(); BB != L->block_end(); ++BB) {
 40     NumBlocks++;
 41   }
 42   errs() << "Loop level " << Nest << " has " << NumBlocks << " blocks\n";
 43 
 44   std::vector<Loop *> SubLoops = L->getSubLoops();
 45   for (Loop::iterator I = SubLoops.begin(), E = SubLoops.end(); I != E; ++I) {
 46     countBlocksInLoop(*I, Nest + 1);
 47   }
 48 }
 49 
 50 void FnBlockCntPluginLegacyPass::getAnalysisUsage(AnalysisUsage &AU) const {
 51   AU.addRequired<LoopInfoWrapperPass>();
 52 }
 53 
 54 static RegisterPass<FnBlockCntPluginLegacyPass> X(
 55     "plugin.fnblockcnt""Function Block Count Plugin Legacy Pass",
 56     falsefalse);

注:

2) 添加 CMakeLists.txt

在目录llvm/lib/Transforms/FnBlockCnt/中新建CMakeLists.txt文件。其内容如下:

add_llvm_library(LLVMFnBlockCntPlugin MODULE FnBlockCntPlugin.cpp PLUGIN_TOOL opt)

注:这里的LLVMFnBlockCntPlugin表示要生成的插件名称为libLLVMFnBlockCntPlugin.so

step 2: 修改 Transforms 目录中的 CMakeLists.txt

llvm/lib/Transforms/CMakeLists.txt文件中添加如下内容:

add_subdirectory(FnBlockCnt)

注:这里的FnBlockCnt即为步骤step 1中新建的目录名称。

step 3: 编译并运行插件

$ ninja -j8
$ opt -load ~/git-projects/llvm-project/build_ninja/lib/LLVMFnBlockCntPlugin.so -disable-output -plugin.fnblockcnt test.bc

执行上述命令后,输出如下:

FnBlockCntPluginLegacyPass --- foo
Loop level 0 has 6 blocks

注: 统计结果中的基本块数量不会计入标签为for.end的基本块。

从上面的输出可以看出,我们以插件方式扩展的 Pass——plugin.fnblockcnt(依赖于 Pass——LoopInfoWrapperPass)实现了统计循环内基本块数量的功能。


References


下一篇:LLVM 之 IR 篇(5):如何基于新 Pass 框架扩展 LLVM IR 优化器

上一篇:LLVM 之 IR 篇(3):如何使用 LLVM IR 优化器

首页