FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

LLVMのバックエンドを作るための第一歩 (11. LLVMが独自ターゲットマシンを認識する仕組み)

f:id:msyksphinz:20190425001356p:plain
LLVM Compiler Infrastructure

MYRISCVXTargetMachineはターゲットマシンを定義するファイルである。

また、MYRISCVXTargetMachineを継承したクラスとして複数のターゲットマシンを作ることができる。 MIPSなどのバイエンディアンアーキテクチャでは、リトルエンディアンとビックエンディアンの両方が定義されるが、RISC-Vではリトルエンディアンしか存在しないので1種類しか不要だ。 ここでは32ビット版と64ビット版のターゲットマシンを作りたいので、MYRISCVX32TargetMachineMYRISCVX64TargetMachineを定義する。

f:id:msyksphinz:20190525161248p:plain
  • MYRISCVXTargetMachine.h
/// MYRISCVX32TargetMachine - MYRISCVX32 little endian target machine.
///
class MYRISCVX32TargetMachine : public MYRISCVXTargetMachine {
  virtual void anchor();
 public:
  MYRISCVX32TargetMachine(const Target &T, const Triple &TT, StringRef CPU,
                          StringRef FS, const TargetOptions &Options,
                          Optional<Reloc::Model> RM,
                          Optional<CodeModel::Model> CM,
                          CodeGenOpt::Level OL, bool JIT);
};

/// MYRISCVX64TargetMachine - MYRISCVX64 little endian target machine.
///
class MYRISCVX64TargetMachine : public MYRISCVXTargetMachine {
  virtual void anchor();
 public:
  MYRISCVX64TargetMachine(const Target &T, const Triple &TT, StringRef CPU,
                          StringRef FS, const TargetOptions &Options,
                          Optional<Reloc::Model> RM,
                          Optional<CodeModel::Model> CM,
                          CodeGenOpt::Level OL, bool JIT);
};

これらのターゲットマシンをLLVM側に認識させるために、LLVMInitializeMYRISCVXTarget()という関数を作る。この関数の名前ルールは決まっており、llcで名前が決まっている。

  • lib/Target/MYRISCVX/MYRISCVXTargetMachine.cpp
extern "C" void LLVMInitializeMYRISCVXTarget() {
  // Register the target.
  //- Little endian Target Machine

  RegisterTargetMachine<MYRISCVX32TargetMachine> X(getTheMYRISCVX32Target());
  RegisterTargetMachine<MYRISCVX64TargetMachine> Y(getTheMYRISCVX64Target());
}

LLVMInitialize(Target名)Target()という初期化用のメソッドはllvm::InitializeAllTargets()から呼ばれる。

  • include/llvm/Support/TargetSelect.h
/// InitializeAllTargets - The main program should call this function if it
/// wants access to all available target machines that LLVM is configured to
/// support, to make them available via the TargetRegistry.
///
/// It is legal for a client to make multiple calls to this function.
inline void InitializeAllTargets() {
  // FIXME: Remove this, clients should do it.
  InitializeAllTargetInfos();

#define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##Target();
#include "llvm/Config/Targets.def"
}

まずは、InitializeAllTargetInfos()が呼ばれる。

namespace llvm {
  /// InitializeAllTargetInfos - The main program should call this function if
  /// it wants access to all available targets that LLVM is configured to
  /// support, to make them available via the TargetRegistry.
  ///
  /// It is legal for a client to make multiple calls to this function.
  inline void InitializeAllTargetInfos() {
#define LLVM_TARGET(TargetName) LLVMInitialize##TargetName##TargetInfo();
#include "llvm/Config/Targets.def"
  }

llvm/Config/Targets.defはCMakeにてビルド用のコンフィグレーションを作成したときに自動的に生成される。 LLVM_TARGETS_TO_BULIDおよびDLLVM_EXPERIMENTAL_TARGETS_TO_BUILDで指定したターゲットについて、初期化用の関数が呼ばれるという仕組みだ。

f:id:msyksphinz:20190525161329p:plain
CMakeのコンフィグレーションからターゲットアーキテクチャが生成される仕組み
  • build-myriscvx/include/llvm/Config/Targets.def
#ifndef LLVM_TARGET
#  error Please define the macro LLVM_TARGET(TargetName)
#endif

LLVM_TARGET(X86)
LLVM_TARGET(Mips)
LLVM_TARGET(MYRISCVX)  # これが展開されて`LLVMInitializeMYRISCVXTargetInfo()`となる。
LLVM_TARGET(RISCV)

#undef LLVM_TARGET

これによりMYRISCVXとしてはLLVMInitializeMYRISCVXTargetInfo()およびLLVMInitializeMYRISCVXTarget()が呼ばれる。

LLVMInitializeMYRISCVXTarget()を見てみるとRegisterTargetMachineにてターゲットをLLVMに登録する。 この際getTheMYRISCVX32Target()およびgetTheMYRISCVX64Target()という関数が呼ばれているが、 これはlib/Target/MYRISCVX/TargetInfo/MYRISCVXTargetInfo.cppで定義されている。getTheMYRISCX{32,64}Target()を一度呼び出すとそのインスタンスが生成され、以降は同じインスタンスが呼び出させる、という仕組みだ。

  • lib/Target/MYRISCVX/TargetInfo/MYRISCVXTargetInfo.cpp
extern "C" void LLVMInitializeMYRISCVXTarget() {
  // Register the target.
  //- Little endian Target Machine

  RegisterTargetMachine<MYRISCVX32TargetMachine> X(getTheMYRISCVX32Target());
  RegisterTargetMachine<MYRISCVX64TargetMachine> Y(getTheMYRISCVX64Target());
}
  • lib/Target/MYRISCVX/TargetInfo/MYRISCVXTargetInfo.cpp
namespace llvm {
Target &getTheMYRISCVX32Target() {
  static Target TheMYRISCVX32Target;
  return TheMYRISCVX32Target;
}

Target &getTheMYRISCVX64Target() {
  static Target TheMYRISCVX64Target;
  return TheMYRISCVX64Target;
}
}

一方でLLVMInitializeMYRISCVXTargetInfo()では以下を実行する。ターゲットアーキテクチャを登録する。

extern "C" void LLVMInitializeMYRISCVXTargetInfo() {
  RegisterTarget<Triple::myriscvx32> X(getTheMYRISCVX32Target(), "myriscvx32",
                                       "MYRISCVX (32-bit)", "MYRISCVX");
  RegisterTarget<Triple::myriscvx64> Y(getTheMYRISCVX64Target(), "myriscvx64",
                                       "MYRISCVX (64-bit)", "MYRISCVX");
}