huolong blog

LLVM框架开发(一)

Linux下LLVM环境配置

  1. 在Ubuntu和Debian环境下,直接使用官方脚本安装,输入下面的命令直接安装LLVM14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 14

注意安装成功后命令会带上版本号,比如命令行下输入clang会提示找不到,但是输入clang-14就可以使用。

  1. 使用包管理器进行安装(推荐)
sudo apt install clang
sudo apt install llvm

使用这种方式安装的llvm和clang输入命令时不需要加版本号即可使用,推荐使用这种方法。

  1. 从源码编译安装 暂时还没用到这种方法,按下不表。

使用旧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的过程中,总是感觉没有很好的抓手,个人认为最好的学习方法是通过实践学习。

  1. 首先必须熟悉LLVM IR表示,LLVM的IR是一种类似RISC汇编的表示形式,整体可读性很高,熟悉IR是进行pass开发的基础。
  2. 熟悉Pass开发API,从文档学习是一种方式,但文档很多地方其实语焉不详,更深入的学习必须通过写Pass后加载查看加载前后ll文件区别来体会API用法