LLVMを使う

quantum computing

LLVMを使う

今回のプロジェクトで利用しているLLVMのバージョンは9.0で最新の物を使っている。LLVMは進化が早いのもあって、ググって出て来た方法を使ってビルドしようとしても失敗したり、一体どーすりゃ良いんだ…orzと思うことが多々ある。そこで、色々と調べたりやってみて分かったことを残しておくことにする。

■ PassManagerの経路

自作のPassManagerで最適化経路を使ったり、何か自前の処理をする時に使うことになる。ググるとこの辺のリポジトリが出てくる。ただコンパイルしようとすると当然失敗する。そんな時は、こんな感じでビルドすればOK。
g++ -shared -fPIC -o libopt.so Skeleton.cpp `llvm-config --cxxflags`
clang -S -Xclang -load -Xclang libskeleton.so test.c 

これでskeleton.soをclangのPassとして経路に使えるようになる。

■ 最適化の実装

LLVMを使って自分で言語を作るとき、出力されるIR・ビットコードの最適化が欲しくなってくる。そこで、どーやれば最適化オプションを追加できるのだろうか…と調べてみたら、こんな感じで実装すると良い(抜粋)。
自分でPass最適化を書かなくても、引数で渡してPassに登録してあげればOK。分かってしまえば非常に簡単にできる。

static cl::opt OptLevelO0("O0", cl::desc("Optimization level 0. Similar to clang -O0"));
static cl::opt OptLevelO1("O1", cl::desc("Optimization level 1. Similar to clang -O1"));
static cl::opt OptLevelO2("O2", cl::desc("Optimization level 2. Similar to clang -O2"));
static cl::opt OptLevelO3("O3", cl::desc("Optimization level 3. Similar to clang -O3"));

static CodeGenOpt::Level GetCodeGenOptLevel() {
  if (OptLevelO1)
    return llvm::CodeGenOpt::Less;
  if (OptLevelO2)
    return llvm::CodeGenOpt::Default;
  if (OptLevelO3)
    return llvm::CodeGenOpt::Aggressive;
  return llvm::CodeGenOpt::None;
}

static TargetMachine* GetTargetMachine(Triple TheTriple, StringRef CPUStr,
                                       StringRef FeaturesStr,
                                       const TargetOptions &Options) {
  std::string Error;
  const Target *TheTarget = TargetRegistry::lookupTarget(MArch, TheTriple, Error);
  if (!TheTarget) {
    return nullptr;
  }

  return TheTarget->createTargetMachine(TheTriple.getTriple(), CPUStr,
                                        FeaturesStr, Options, getRelocModel(),
                                        getCodeModel(), GetCodeGenOptLevel());
}

int main(int argc, char **argv) {
  llvm::InitLLVM X(argc, argv);
  int optimize = 0;

  option_parser_t parser;
  parser.help(&suggest_help);
  parser.option('h', "help", 0, [&](const char* s){help(0);});
  parser.option('O', 0, 1, [&](const char* s){optimize = atoi(s);});
  auto argv1 = parser.parse(argv);
  std::vector htif_args(argv1, (const char*const*)argv + argc);

  if (!*argv1)
    help(); 

  llvm::LLVMContext Context;
  llvm::InitializeAllTargets();
  llvm::InitializeAllTargetMCs();
  llvm::InitializeAllAsmPrinters();
  llvm::InitializeAllAsmParsers();

  // optimize legacy code
  llvm::PassRegistry &Registry = *llvm::PassRegistry::getPassRegistry();
  llvm::initializeCore(Registry);
  llvm::initializeCoroutines(Registry);
  llvm::initializeIPO(Registry);
  llvm::initializeAnalysis(Registry);
  llvm::initializeTransformUtils(Registry);
  llvm::initializeInstCombine(Registry);

  llvm::Triple ModuleTriple(frontend.getModule()->getTargetTriple());
  std::string CPUStr, FeaturesStr;
  TargetMachine *Machine = nullptr;
  const TargetOptions Options = InitTargetOptionsFromCodeGenFlags();

  if (ModuleTriple.getArch()) {
    CPUStr = getCPUStr();
    FeaturesStr = getFeaturesStr();
    Machine = GetTargetMachine(ModuleTriple, CPUStr, FeaturesStr, Options);
  }

  std::unique_ptr TM(Machine);
  setFunctionAttributes(CPUStr, FeaturesStr,*frontend.getModule());
  pm.add(createTargetTransformInfoWrapperPass(TM ? TM->getTargetIRAnalysis() : TargetIRAnalysis()));

  std::unique_ptr FPasses;
  FPasses.reset(new legacy::FunctionPassManager(frontend.getModule()));
  FPasses->add(createTargetTransformInfoWrapperPass(TM ? TM->getTargetIRAnalysis() : TargetIRAnalysis()));

  llvm::PassManagerBuilder Builder;
  Builder.OptLevel = optimize;
  Builder.SizeLevel = 0;
  Builder.Inliner = llvm::createFunctionInliningPass(Builder.OptLevel, Builder.SizeLevel, false);
  Builder.populateFunctionPassManager(*FPasses);
  Builder.populateModulePassManager(pm);
  // 自分で何か経路を追加するときは、ここに追加
  // registercustomOptimizerPass(Builder, pm);

  return 0;
}

今回、qlangという独自の言語を作っているわけだけど、上記のような実装をすることで、独自の言語にも最適化オプションを付与することが出来た。
qlang -O[0-3] test.q

IRを見て確認してみる。まずはソースコードはシンプルなもので。
cat test.q

func main() {
  qint x, y;
  x = 0;
  
  while x <= 10 {
    write x;
    x = x + 1;
  }
# 最適化無しコンパイル

qlang test.q

; ModuleID = 'top'
source_filename = "top"

declare i64 @write(i64)

declare i64 @writeln()

define i64 @main() {
entrypoint:
  %x = alloca i64
  %y = alloca i64
  store i64 0, i64* %x
  br label %while.cond

while.cond:                                       ; preds = %while.body, %entrypoint
  %0 = load i64, i64* %x
  %1 = icmp sle i64 %0, 10
  br i1 %1, label %while.body, label %while.merge

while.body:                                       ; preds = %while.cond
  %2 = load i64, i64* %x
  %3 = call i64 @write(i64 %2)
  %4 = load i64, i64* %x
  %5 = add i64 %4, 1
  store i64 %5, i64* %x
  br label %while.cond

while.merge:                                      ; preds = %while.cond
  call void asm sideeffect "qtelep.k  qa0, qt1, qzero, 0", ""() #0
  ret i64 0
}

attributes #0 = { nounwind }
# -O3最適化コンパイル

qlang -O3 test.q

; ModuleID = 'top'
source_filename = "top"

declare i64 @write(i64) local_unnamed_addr

define i64 @main() local_unnamed_addr {
entrypoint:
  %0 = tail call i64 @write(i64 0)
  %1 = tail call i64 @write(i64 1)
  %2 = tail call i64 @write(i64 2)
  %3 = tail call i64 @write(i64 3)
  %4 = tail call i64 @write(i64 4)
  %5 = tail call i64 @write(i64 5)
  %6 = tail call i64 @write(i64 6)
  %7 = tail call i64 @write(i64 7)
  %8 = tail call i64 @write(i64 8)
  %9 = tail call i64 @write(i64 9)
  %10 = tail call i64 @write(i64 10)
  tail call void asm sideeffect "qtelep.k  qa0, qt1, qzero, 0", ""() #0
  ret i64 0
}

attributes #0 = { nounwind }

ばっちり最適化が効いている。

■ アセンブリをLLVMでcall追加

これが地味に面倒だった。例えばCで書くとき、こんなコードがあったとする

int main() {
  asm ("addi a5, a5, 10");
  return 0;
}

アセンブリをLLVMの処理としてどうやってcallするのか?というのはこんな感じで実現できる。これも非常にシンプル。LLVM便利でサイコーすぎる。

  llvm::Type *ResultType;
  ResultType = llvm::Type::getVoidTy(context);
  llvm::FunctionType *funcType = llvm::FunctionType::get(ResultType, false);

  char asmstr[128];
  sprintf(asmstr, "addi  %s, %s, 10", "a5", "a5");

  bool hasSideEffect = true;
  llvm::InlineAsm::AsmDialect asmDialect = llvm::InlineAsm::AD_ATT;
  std::string constraints = "";
  llvm::InlineAsm *ia = llvm::InlineAsm::get(funcType, asmstr, constraints, hasSideEffect, false, asmDialect);
  llvm::CallInst *result = builder.CreateCall(ia);
  result->addAttribute(llvm::AttributeList::FunctionIndex, llvm::Attribute::NoUnwind);

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です