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 框架以便更深入地研究相关内容。
本节通过实现一个用于统计函数参数个数的 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 false, false);
注:
新建的目录名称不必是FnArgCnt
,也可以是其它名称。同样地,新建的源文件名称不必是FnArgCntPlugin.cpp
,也可以是其它名称。
第 9 行,自定义 Pass 类FnArgCntPluginLegacyPass
派生自FunctionPass
类,表示一次仅对一个函数进行分析(即过程内分析)。另外,类FunctionPass
的子类必须实现一个函数原型为bool runOnFunction(Function &F);
的虚函数。
第 15 行,静态数据成员ID
的作用:其内存地址作为 Pass 的唯一标识符,从而避免昂贵的 C++ 运行时开销。
第 25 行,函数runOnFunction()
的返回值用于表示是否修改了函数(比如:删除了函数内的某个基本块)。如果值为false
,则表示未修改。
第 28~30 行,用于以插件方式注册基于传统 Pass 框架的 Pass。类RegisterPass
的模板参数为 Pass 的类名(这里为FnArgCntPluginLegacyPass
)。
其构造函数的第一个参数表示 Pass 命令行选项的全称(这里为"plugin.fnargcnt"
,可以通过opt
工具查看,详见下文),第二个参数表示 Pass 命令行选项的描述(这里为"Function Argument Count Plugin Legacy Pass"
,也可以通过opt
工具查看),第三个参数表示是否为仅依赖程序控制流图进行分析的 Pass(这里为false
,表示不是仅依赖程序控制流图进行分析的 Pass),第四个参数表示是否为 Analysis Pass(这里为false
,表示不是 Analysis Pass)。
注: 第三个参数和第四个参数的值都为true
时,测试程序test.bc
也可以正常运行并输出相同的结果。
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
从上面的输出可以看出:
plugin.fnargcnt
,即我们以插件方式扩展的自定义 Pass 的命令行选项全称。
Function Argument Count Plugin Legacy Pass
,即我们以插件方式扩展的自定义 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
实现了统计函数参数个数的功能。
本节介绍了如何基于传统 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", false, false)
34
35 FunctionPass *llvm::createFnArgCntLegacyPass() {
36 return new FnArgCntLegacyPass();
37 }
注:
第 18 行,用于在源码中扩展时注册基于传统 Pass 框架的 Pass。函数initializeXXXPass()
中的XXX
必须为 Pass 的类名(这里为FnArgCntLegacyPass
)。
第 32~33 行,当不依赖其它 Pass 时,通过调用宏定义INITIALIZE_PASS
以实现函数initializeXXXPass()
的定义。
第 35~37 行,定义创建 Pass 实例的方法。该函数名称不必是createFnArgCntLegacyPass
,也可以是其它名称。
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 框架以插件方式进行扩展。
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 false, false);
注:
第 10 行,自定义 Pass 类FnBlockCntPluginLegacyPass
派生自FunctionPass
类,表示一次仅对一个函数进行分析(即过程内分析)。另外,类FunctionPass
的子类必须实现一个函数原型为bool runOnFunction(Function &F);
的虚函数。
第 30 行,通过调用所依赖的 Pass——LoopInfoWrapperPass
获取循环信息,用于后续统计循环内的基本块数量。
第 31~33 行,遍历所有的循环,并统计每个循环中的基本块数量。
第 37~48 行,统计一个循环及其内部嵌套的循环的基本块数量。
第 50~52 行,用于添加自定义 Pass 类FnBlockCntPluginLegacyPass
所依赖的 Passes。注意: 如果不添加第 50~52 行的代码,那么程序在执行第 30 行时会崩溃退出。
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
)实现了统计循环内基本块数量的功能。
下一篇:LLVM 之 IR 篇(5):如何基于新 Pass 框架扩展 LLVM IR 优化器