【LLVM】编写自己的pass
LLVM的Pass框架是LLVM中的重要部分,多个pass一起完成了LLVM的优化与代码转换工作。每个pass都会完成指定的优化工作。在某些情况下,pass之间会有对应的依赖关系,即新的pass会与其他原有的pass产生关联:例如在运行此pass之前必须先运行某一个pass。类似这种情况,都有相应的接口可以实现。参考这里。
另外还有一种情况,当我们需要一种更高的level去控制pass时,比如将特定功能的pass进行分组,一个pass可以被重复注册到不同的分组中去。参考这里
如上介绍的那样,LLVM的Pass架构具有良好的可重用性与控制性。它允许我们嵌入自己编写的pass或者关闭一些默认提供的pass。同时,单个pass的开发以及调试也很独立,所以不必担心破坏整个LLVM的源码结构,只需要学会操作LLVM IR即可实现自己的pass。
Pass的分类
所有的pass可以分成两类:分析和转换。
分析类的pass以提供信息为主,转换类的pass会修改中间代码。可以实现的具体的pass类型如下所示:
ImmutablePass
ModulePass
CallGraphSCCPass
FunctionPass
LoopPass
RegionPass
BasicBlockPass
MachineFunctionPass
通过集成指定的类实现相关的虚函数即可。以上的链接对每一种pass的作用范围以及实现途径做了一个简单的介绍。
编写Pass
首先在llvm/lib/Transforms
目录下创建TestPass目录:
cd到TestPass目录下,创建三个文件:
CMakeLists.txt:
add_llvm_loadable_module( LLVMMyPass
MyPass.cpp
)
Makefile:
LEVEL = ../../..
LIBRARYNAME = LLVMTest
LOADABLE_MODULE = 1
USEDLIBS =
include $(LEVEL)/Makefile.common
testPass.cpp:
#define DEBUG_TYPE "hello"
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Instructions.h"
using namespace llvm;
namespace {
// Hello - The first implementation, without getAnalysisUsage.
struct Hello : public FunctionPass {
static char ID; // Pass identification, replacement for typeid
Hello() : FunctionPass(ID) {}
virtual bool runOnFunction(Function &F) {
//主要是打印出函数名称
errs() << "Hello: ";
errs() << F.getName() << '\n';
Function *tmp = &F;
// 遍历函数中的所有基本块
for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) {
// 遍历基本块中的每条指令
for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) {
// 是否是add指令
if (inst->isBinaryOp()) {
if (inst->getOpcode() == Instruction::Add) {
ob_add(cast<BinaryOperator>(inst));
}
}
}
}
return false;
}
// a+b === a-(-b)
bool ob_add(BinaryOperator *bo) {
BinaryOperator *op = NULL;
if (bo->getOpcode() == Instruction::Add) {
// 生成 (-b)
op = BinaryOperator::CreateNeg(bo->getOperand(1), "", bo);
// 生成 a-(-b)
op = BinaryOperator::Create(Instruction::Sub, bo->getOperand(0), op, "", bo);
op->setHasNoSignedWrap(bo->hasNoSignedWrap());
op->setHasNoUnsignedWrap(bo->hasNoUnsignedWrap());
}
// 替换所有出现该指令的地方
bo->replaceAllUsesWith(op);
}
};
}
char Hello::ID = 0;
static RegisterPass<Hello> X("hello", "Hello World Pass");
都写好之后cd ..
退出到上层目录下,修改CMakeLists.txt文件,添加add_subdirectory(TestPass)
语句。
接下来进入到之前的build文件目录下,直接在终端运行make命令。待编译结束后会生成一个LLVMMyPass.dylib
文件,它在build/lib
文件夹下。
运行编写的Pass
首先自己随便写一个.c
文件:
test.c:
include<stdio.h>
int sum(int a, int b) {
return a+b;
}
int main() {
int a = 10,b = 10;
a = sum(a,b);
printf("%d",a);
}
使用命令将test.c
文件生成test.bc
文件,生成步骤见这里
将编写的pass作用到对应的test.bc
文件上:
./opt -load ../lib/LLVMMyPass.dylib -test < test.bc > charge_test.bc
这样即可使用opt工具完成pass的功能,此pass的作用是实现了一个转换的pass,它将程序中的加法转换成了减法。即A+B
-> A-(-B)
。
对于pass作用后生成的charge_test.bc
文件,可以使用如下命令进行查看(因为.bc字节码文件阅读性太差,几乎不可阅读):
- 生成对应的汇编文件(使用汇编代码查看):
llc charge_test.bc -o charge_test.s
- 生成对应的IR代码文件(.ll文件):
lli charge_test.bc
这样就可以将pass作用后的文件与没作用的文件进行对比,查看在底层是否已达到想要的效果。