【LLVM】如何写一个pass
1.简介
LLVM pass是编译器中最有趣的部分,能够对代码进行转化和优化。所有pass都是Pass类的子类,通过覆盖Pass类的虚函数来实现功能,可继承的类有ModulePass , CallGraphSCCPass, FunctionPass , LoopPass, RegionPass, BasicBlockPass。
环境搭建参考:https://blog.csdn.net/l2563898960/article/details/82871826
本文参考:https://llvm.org/docs/WritingAnLLVMPass.html
2.写hello world pass
Hello pass用于打印出编译时非外部函数的函数名,不会修改程序,只是监视作用,Hello pass的源码和文件在LLVM源码的lib/Transforms/Hello目录。
(1)设置
首先,配置和安装LLVM;然后在LLVM源码目录下创建一个新目录,这里假设你创建了lib/Transforms/Hello目录;最后,设置build脚本,用于编译新的pass。将以下代码拷贝到lib/Transforms/Hello/CMakeLists.txt。
add_llvm_library( LLVMHello MODULE
Hello.cpp
PLUGIN_TOOL
opt
)
将以下行加入到lib/Transforms/CMakeLists.txt
add_subdirectory(Hello)
这个build脚本表示当前目录的Hello.cpp文件将被编译和链接成共享对象$(LEVEL)/lib/LLVMHello.so,能被opt工具通过-load选项动态加载。
(2)写Helllo.cpp
首先需要添加头文件,因为在写 Pass,在函数 Function上操作,且需要打印数据。
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
接下来,include文件中的函数落在llvm命名空间。
using namespace llvm;
接下来,开始一段匿名空间,匿名空间在c++中用到,c中采用static实现,定义在匿名空间中的变量只能在当前文件可见。
namespace{
接下来定义pass,定义Hello类,从FunctionPass类继承过来。
struct Hello : public FunctionPass {
static char ID;
Hello() : FunctionPass(ID) {}
声明 runOnFunction 方法,覆盖了从 FunctionPass继承来的虚函数。runOnFunction()就是以函数为单位进行处理,LLVM会以一次一个function为单位,喂进来给你处理,一下来吗就是将喂进来的function的名字打印出来。
bool runOnFunction(Function &F) override {
errs() << "Hello: ";
errs().write_escaped(F.getName()) << '\n';
return false;
}
}; // end of struct Hello
} // end of anonymous namespace
初始化pass的ID,LLVM使用ID地址来识别pass,所以所用初始化的值不重要。
char Hello::ID = 0;
最后,注册Hello类,给一个命令行参数"hello"和一个名字"Hello World Pass",最后两个参数描述它的行为:如果pass需要修改CFG则第3个参数设为true,若pass是一个analysis pass,例如dominator tree pass,则第4个参数设为true。
static RegisterPass<Hello> X("hello", "Hello World Pass",
false /* Only looks at CFG */,
false /* Analysis Pass */);
整个代码.cpp文件如下:
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.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 */);
在顶层LLVM根目录下创建build目录,在build目录下先"cmake ../",再"make"即可生成"lib/LLVMHello.so文件"。
(3)使用opt运行pass
生成共享目标文件后,由于已经注册过了,所以可以使用opt命令通过你的pass运行LLVM程序。首先参考Getting Started with the LLVM System编译"Hello World"为bitcode文件(hello.bc),通过以下命令运行hello.bc("-load"表示加载pass库):
$ opt -load lib/LLVMHello.so -hello < hello.bc > /dev/null
Hello: __main
Hello: puts
Hello: main
3.Pass类和需求
设计一个pass首先要考虑需要继承哪一个类,Hello World例子使用了FunctionPass类来实现,接下来看看其它可用的类。
(1)ImmutablePass类
这个类比较无聊,这个类用在不用运行不会改变状态不需要更新的pass,在转化和分析中不常用到,但能提供当前编译器配置信息。尽管这个类不常用到,但是能提供当前被编译的目标机的信息,以及影响转化的静态信息。
(2)ModulePass类
最常用的一个类,使用该类表示将整个程序当做一个单元,可以随意引用函数主体,添加和移除函数。由于不知道ModulePass子类的行为,不能作优化。
(3)CallGraphSCCPass类
在调用图上从后往前遍历程序。
(4) FunctionPass类
FunctionPass处理程序中每个函数,并不依赖其他函数,FunctionPass不需要它们按特定顺序执行,不会修改外部函数。
(5) LoopPass类
LoopPass处理函数中的loop,并不依赖函数中其他loop。
(6)RegionPass
处理函数中单入口单退出的区域。
(7)BasicBlockPass类
类似FunctionPass,但只处理和修改单个基本块。
(8)MachineFunctionPass
依赖机器类型。
(9) getAnalysisUsage方法
不同pass之间交互。
(10)RegisterAnalysisGroup模板
(11)Statistic类