pass简介

pass:llvm pass 是 llvm 架构的重要组成部分。pass 的主要工作是:对代码进行分析、优化代码。所有 llvm pass 都是pass的子类,通过通过重写从中继承的虚方法来实现功能Pass。LLVM Pass框架的一个主要特性是它根据传递遇到的约束(由它们派生自哪个类来指示)来调度传递以高效的方式运行。

pass大致可以分为两类:分析和转换。分析类的pass以提供信息为主,转换类的则要修改中间代码。

pass类如下:

  • ImmutablePass:不运行, 不改变状态也永不更新的pass, 一般情况下用于显示编译器的配置信息.

  • ModulePass:用于执行任何非结构化的过程间优化。通用类型的pass,将整个程序示为一个单元处理。用virtual bool runOnModule(Module &M) = 0;来实现ModulePass.

  • CallGraphSCCPass:用于被那些需要从底向上(bottom-up)遍历call graph的pass调用.

  • FunctionPass:是以单个函数为作用域的pass, 每个函数间是相互独立的, 相互之间无法影响

  • LoopPass:是以单个loop为作用域的pass, 每个loop间相互独立. LoopPass以嵌套方式处理循环, 外层循环最后处理.

  • RegionPass:类似与LoopPass,但是在函数中的每个单入口单出口区域执行。 RegionPass以嵌套顺序处理,以便最后处理最外面的区域。p

  • BasicBlockPass:用于实现本地优化,优化通常每次针对一个基本块或指令运行。

  • MachineFunctionPass:类似FunctionPass, 区别在于前者属于LLVM code generator(后端), 生成架构相关代码, FunctionPass属于LLVM optimizer(中端), 生成通用的IR.

pass”入门”

编写/分析

1
2
3
$ cd ~/llvm/llvm-7.0.1.src/lib/Transforms/Hello
$ ls
CMakeLists.txt Hello.cpp Hello.exports

https://llvm.org/docs/WritingAnLLVMPass.html

可以参考官方教程再自己搞一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"

using namespace llvm;

namespace {
struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}

bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);

static RegisterStandardPasses Y(
PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder,
legacy::PassManagerBase &PM) { PM.add(new Hello()); });

对上述代码的一些分析:

1
namespace{

开始一个新的匿名命名空间。在C++中匿名命名空间会引入静态全局作用域,类似C语言中的static关键字,它使在匿名命名空间内声明的内容仅对当前文件可见。

接着,声明我们的Pass

1
struct Hello : public FunctionPass {

声明了一个Hello类,它是FunctionPass的子类。FunctionPass类一次只操作一个函数。

1
2
static char ID;
Hello() : FunctionPass(ID) {}

声明一个 pass 的 ID,llvm 将会使用ID来定位这些pass。避免使用复杂的C++运行机制。

1
2
3
4
5
6
7
  bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace

定义runOnFunction方法,覆写从FunctionPass继承的虚函数。

1
char Hello::ID = 0;

初始化Pass ID。LLVM使用ID的地址来标识Pass,因此初始化值并不重要。

1
2
3
static RegisterPass<Hello> X("hello", "Hello World Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);

注册类Hello,第一个命令行参数hello,并命名为Hello World Pass。最后两个参数描述了它的行为:如果传递遍历CFG而不修改它,则第三个参数设置为true; 如果pass 是分析 pass,那么将会有第四个参数,为true,反之则为false.

1
2
$ cd build
$ make

在我们刚刚创建的build内,执行 make,得到一个新文件LLVMHello.so

我们在~/llvm/build/lib下可以找到对应的文件。

使用opt命令运行pass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ clang -S -O3 -emit-llvm hello.c
$ opt -load LLVMHello.so -hello < hello.ll > /dev/null
Hello: main
$ opt -load LLVMHello.so -hello -time-passes < hello.bc > /dev/null
Hello: main
===-------------------------------------------------------------------------===
... Pass execution timing report ...
===-------------------------------------------------------------------------===
Total Execution Time: 0.0001 seconds (0.0001 wall clock)

---User Time--- --User+System-- ---Wall Time--- --- Name ---
0.0001 ( 56.3%) 0.0001 ( 56.3%) 0.0001 ( 56.4%) Bitcode Writer
0.0000 ( 37.0%) 0.0000 ( 37.0%) 0.0000 ( 37.8%) Hello World Pass
0.0000 ( 6.7%) 0.0000 ( 6.7%) 0.0000 ( 5.8%) Module Verifier
0.0001 (100.0%) 0.0001 (100.0%) 0.0001 (100.0%) Total

===-------------------------------------------------------------------------===
LLVM IR Parsing
===-------------------------------------------------------------------------===
Total Execution Time: 0.0001 seconds (0.0001 wall clock)

---User Time--- --User+System-- ---Wall Time--- --- Name ---
0.0001 (100.0%) 0.0001 (100.0%) 0.0001 (100.0%) Parse IR
0.0001 (100.0%) 0.0001 (100.0%) 0.0001 (100.0%) Total

我们可以使用opt命令通过您的Pass来运行LLVM程序。由于您已使用RegisterPass注册了Pass,因此一旦加载,您就可以使用 opt 工具访问它。

-load选项指定 Pass 作为共享对象加载,加载后-hello才是有效的。(这也是注册Pass的原因之一)。因为 -hello没有修改任何东西,所以直接查看结果即可。

也可以通过 opt -load LLVMHello.so-help |grep -i hello来查看其他注册字符串,当然最后也可以查看一下运行时间。

大致了解了 pass 的流程和编写骨架吧。

参考:

https://llvm.org/docs/WritingAnLLVMPass.html

https://www.cnblogs.com/Five100Miles/