FPGA開発日記

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

オリジナルLLVMバックエンド実装をまとめる(5. LLVM IRのret文からreturn命令を生成するまでの流れ)

非常にシンプルな関数をどのようにして命令に変換するかについて考える。

int main()
{
  return 0;
}
./bin/clang -c -O2 ../myriscvx-tests/tests/simple_main.cpp -emit-llvm
./bin/llvm-dis simple_main.bc -o -
define dso_local i32 @main() local_unnamed_addr #0 {
entry:
  ret i32 0
}

ここで共通の処理として登場するのは、関数の呼び出し時の処理と、関数から戻るときの処理だ。

関数の呼び出し処理とは、つまり引数の処理だ。関数コールを行った側から引数を正しく受け取り、関数内で使えるようにする。このための処理はLowerFormalArguments()に実装する。

一方で、関数から戻る処理は、戻り値を所定の場所に格納し、戻り場所までジャンプする。このための処理はLowerReturnに実装する(図[fig:LowerFormalArguments/LowerReturn])

//@LowerFormalArguments {
/// LowerFormalArguments - transform physical registers into virtual registers
/// and generate load operations for arguments places on the stack.
SDValue
MYRISCVXTargetLowering::LowerFormalArguments(SDValue Chain,
                                             CallingConv::ID CallConv,
                                             bool IsVarArg,
                                             const SmallVectorImpl<ISD::InputArg> &Ins,
                                             const SDLoc &DL, SelectionDAG &DAG,
                                             SmallVectorImpl<SDValue> &InVals)
const {

  return Chain;
}
// @LowerFormalArguments }

//===----------------------------------------------------------------------===//
//@              Return Value Calling Convention Implementation
//===----------------------------------------------------------------------===//

SDValue
MYRISCVXTargetLowering::LowerReturn(SDValue Chain,
                                    CallingConv::ID CallConv, bool IsVarArg,
                                    const SmallVectorImpl<ISD::OutputArg> &Outs,
                                    const SmallVectorImpl<SDValue> &OutVals,
                                    const SDLoc &DL, SelectionDAG &DAG) const {
  return DAG.getNode(MYRISCVXISD::Ret, DL, MVT::Other,
                     Chain, DAG.getRegister(MYRISCVX::RA, Subtarget.getXLenVT()));
}

とりあえず空っぽだ。LowerFormalArgumentsに関しては何もせず、LowerReturnに関しては、Retノード命令を使ってSelectionDAGへと変換していることが分かる。 つまり、LLVM IRのretが呼び出されると、このLowerReturn()が実行され、MYRISCVXISD::Retノードを生成する(MYRISCVXISD::RetMYRISCVXInstrInfo.tdで定義したノードだ)。

このMYRISCVXISD::Retをどうするかというと、MYRISCVXInstrInfo.tdに以下のルールを追加してRetRAノードに変換する。

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

MYRISCVXPseudoはその名の通り、具体的な命令を生成せずに、等価なノードを生成するだけだ。 この等価なRetRAノードをどうするかというと、さらにLLVMにあらかじめ組み込まれているexpandPostRAPsudo()というメソッドを拡張して、RetRAノードに遭遇した場合は等価なret命令(つまりJALR x0, ra, 0)に変換するという訳だ。

  • llvm-myriscvx80/include/llvm/CodeGen/TargetInstrInfo.h
  /// 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.
  virtual bool expandPostRAPseudo(MachineInstr &MI) const { return false; }
  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
//@expandPostRAPseudo
/// Expand Pseudo instructions into real backend instructions
bool MYRISCVXInstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
  //@expandPostRAPseudo-body
  MachineBasicBlock &MBB = *MI.getParent();

  switch (MI.getDesc().getOpcode()) {
    default:
      return false;
    case MYRISCVX::RetRA:
      expandRetRA(MBB, MI);
      break;
  }

  MBB.erase(MI);
  return true;
}


void MYRISCVXInstrInfo::expandRetRA(MachineBasicBlock &MBB,
                                    MachineBasicBlock::iterator I) const {
  BuildMI(MBB, I, I->getDebugLoc(), get(MYRISCVX::JALR))
      .addReg(MYRISCVX::ZERO).addReg(MYRISCVX::RA).addImm(0);
}
f:id:msyksphinz:20190919012148p:plain
LLVM IRのret命令がMYRISCVXのReturn命令に変換されるフロー

これで、以下の命令列が生成されるようになる(Calling Conventionを実装していないのでまだ戻り値を生成できていない)。

main:
        .cfi_startproc
        .frame  $x2,0,$x1
        .mask   0x00000000,0
        .set    noreorder
        .set    nomacro
        discovered a new reachable node %bb.0
# %bb.0:                                # %entry
        ret