FPGA開発日記

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

オリジナルLLVM Backendを追加しよう (23. 命令の定義からAsmPrinterへの変換プロセスまとめ)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

LLVMには独自のIRを定義できる仕組みがあり、それを使って命令を変換したりして最終的に命令を生成する。 例えば、Return命令をどのようにして最終的にllcが命令として出力するのかについてまとめる。

例えば、MYRISCVXRetという関数から戻る命令のためのIRを定義する。 ISDというのは、SelectionDAGノードの型を示すものらしい。

  • lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Return
def MYRISCVXRet : SDNode<"MYRISCVXISD::Ret", SDTNone,
                         [SDNPHasChain, SDNPOptInGlue, SDNPVariadic]>;

これはLLVMの実装からは、MYRISCVXISD::Retとして参照できるようになる。 DAGは、以下の定義によって使用される。

  • lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
let isReturn=1, isTerminator=1, hasDelaySlot=0, isBarrier=1, hasCtrlDep=1 in
  def RetRA : MYRISCVXPseudo<(outs), (ins), "", [(MYRISCVXRet)]>;

RetRAノードを定義する。isReturnとisTerminatorはリターン命令であり、BasicBlockの最後のノードである、ということを示していると思う。

このMYRSCVXISD::RetノードはC言語のreturn命令などで使用されるのだが、Instruction Selectionの段階でMYRISCVX::RetRAに変換される。これは同等なので変換しても良い。

ISEL: Starting selection on root node: t62: ch = MYRISCVXISD::Ret t61
Selecting: t62: ch = MYRISCVXISD::Ret t61
ISEL: Starting pattern match
  Morphed node: t62: ch = RetRA t61
ISEL: Match complete!

このMYRISCVX::RetRAは、以下のMYRISCVXSEISelLowering.cppの実装によってMYRISCVX::JALRに変換される。 これが最終的にjalr raという命令に変換されるわけだ。

llvm.org

This function is called for all pseudo instructions that remain after register allocation. Many pseudo instructions are created to help register allocation. This is the place to convert them into real instructions. The target can edit MI in place, or it can insert new instructions and erase MI. The function should return true if anything was changed.

//@expandPostRAPseudo
/// Expand Pseudo instructions into real backend instructions
bool MYRISCVXSEInstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
  //@expandPostRAPseudo-body
  MachineBasicBlock &MBB = *MI.getParent();
  switch (MI.getDesc().getOpcode()) {
    default:
      return false;
    case MYRISCVX::RetRA:
      expandRetLR(MBB, MI);
      break;
  }
  MBB.erase(MI);
  return true;
}

void MYRISCVXSEInstrInfo::expandRetLR(MachineBasicBlock &MBB,
                                      MachineBasicBlock::iterator I) const {
  BuildMI(MBB, I, I->getDebugLoc(), get(MYRISCVX::JALR)).addReg(MYRISCVX::RA);
}

Cpu0の命令変換の説明では、以下のように書いてある。