LLVM框架开发(一)
Linux下LLVM环境配置
- 在Ubuntu和Debian环境下,直接使用官方脚本安装,输入下面的命令直接安装LLVM14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 14
注意安装成功后命令会带上版本号,比如命令行下输入clang会提示找不到,但是输入clang-14就可以使用。
- 使用包管理器进行安装(推荐)
sudo apt install clang
sudo apt install llvm
使用这种方式安装的llvm和clang输入命令时不需要加版本号即可使用,推荐使用这种方法。
- 从源码编译安装 暂时还没用到这种方法,按下不表。
使用旧PassManager开发第一个Pass开发
参考官方经典案例Hello Pass:
#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;
}
};
}
char Hello::ID = 0;
// Register for opt
static RegisterPass<Hello> X("hello", "Hello World Pass");
// Register for clang
static RegisterStandardPasses Y(PassManagerBuilder::EP_EarlyAsPossible,
[](const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) {
PM.add(new Hello());
});
如果出现include文件找不到错误,请在.bashrc文件中指明LLVM头文件的路径,命令如下:
export CPLUS_INCLUDE_PATH=CPLUS_INCLUDE_PATH:"/usr/include/llvm":"/usr/include/llvm-c"
写好代码后我们可以使用命令行输入命令进行编译,在初学阶段建议使用命令行编译,能够了解一些编译选项,在熟练后建议使用cmake进行编译。
clang++ -fno-rtti -fPIC -shared Hello.cpp -o Hello.so
如果不想设置bashrc,可以直接使用以下命令编译:
clang++ `llvm-config --cxxflags` -fno-rtti -fPIC -shared Hello.cpp -o Hello.so
之后可以使用opt动态加载bc文件或ll文件,由于高版本llvm默认使用新的passmanager,所以opt加载命令会在下文介绍。不过使用clang加载也是可以的,并且也很方便:
clang++ -Xclang -load -Xclang Hello.so main.cpp -o main
使用新的PassManager开发Pass
代码如下:
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
struct HelloNewPMPass : public PassInfoMixin<HelloNewPMPass> {
PreservedAnalyses run(Function &F,
FunctionAnalysisManager &FAM) {
if(F.hasName())
errs() << "Hello " << F.getName() << "\n";
return PreservedAnalyses::all();
// 返回all表示未作改动
}
};
} // end anonymous namespace
extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK
llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION, "HelloNewPMPass", "v0.1",
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
// hellopass为自定义命令行编译选项
if(Name == "hellopass"){
FPM.addPass(HelloNewPMPass());
return true;
}
return false;
}
);
}
};
}
同样使用clang++编译成so文件,命令同上。
使用clang编译成ll文件或者bc文件,ll文件是便于人阅读的类似汇编语言的形式,而bc则是字节码形式,命令如下:
clang++ -emit-llvm -S main.cpp -o main.ll
clang++ -emit-llvm -c main.cpp -o main.bc
可以使用opt命令来加载so库并对ll文件或bc文件进行处理,命令如下:
opt -load-pass-plugin=/path -passes="hellopass" -S main.ll -o hellomain.ll
最终使用clang将ll或者bc文件转换为可执行文件:
clang++ hellomain.ll -o hellomain
Pass开发学习流程
LLVM官方文档写的还是比较详细的,但是在学习写pass的过程中,总是感觉没有很好的抓手,个人认为最好的学习方法是通过实践学习。
- 首先必须熟悉LLVM IR表示,LLVM的IR是一种类似RISC汇编的表示形式,整体可读性很高,熟悉IR是进行pass开发的基础。
- 熟悉Pass开发API,从文档学习是一种方式,但文档很多地方其实语焉不详,更深入的学习必须通过写Pass后加载查看加载前后ll文件区别来体会API用法