FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (16. ISELLowering内でのReturn文の取り扱い)

MYRISCVXISelLoweringLLVM IRからSelectionDAG(データフローグラフ)への変換プロセスになる。 バックエンドのかなり初期の部分で適用される。

ここで必要な実装はMYRISCVXISelLowering.cppを実装する必要がある。 ここではLowerReturnLowerFormalArgumentsを実装した。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
//@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, MVT::i32));
}

ただし、このままではLowerReturnは正しく動作しない。また、MIPSなどと同様にMYRISCVXにもCalling Conventionが存在し、引数を渡す際にも特定のレジスタを使用する必要がある。 MYRISCVXの場合はRISC-Vと同様に整数の引数は汎用レジスタA0-A7に格納して渡す必要がある。 このCalling Conventionを実現するのがMYRISCVXCallingConv.tdに記述している以下のCCIfTypeおよびCCAssignToRegである。

以下の記述では、関数の戻り値が渡される場合に、CCAssignToRegで示されるレジスタのどれかに引数が格納されるルールが追加されている。 そしてCalling ConventionであるRetCC_MYRISCVXを定義する。

f:id:msyksphinz:20190601165037p:plain
関数戻り時の戻り値を特定のレジスタ経由で行う仕組み
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXCallingConv.td
def RetCC_MYRISCVXEABI : CallingConv<[
  // i32 are returned in registers A0, A1
  CCIfType<[i32], CCAssignToReg<[A0, A1]>>
]>;

def RetCC_MYRISCVX : CallingConv<[
  CCDelegateTo<RetCC_MYRISCVXEABI>
]>;

このRetCC_MYRISCVXはInstruction Selection時のAnalyzeReturnで使用される。 AnalyzeReturnLowerReturnから呼び出されるのだが、RetCC_MYRISCVXの実装を見てみると、i32以外の型を使用する(今のところは未サポート)、もしくは該当のレジスタに割り付けが失敗するとTrueを返し、Abortするような仕組みにしている。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
template<typename Ty>
void MYRISCVXTargetLowering::MYRISCVXCC::
analyzeReturn(const SmallVectorImpl<Ty> &RetVals, bool IsSoftFloat,
              const SDNode *CallNode, const Type *RetTy) const {
  CCAssignFn *Fn;

  Fn = RetCC_MYRISCVX;

...
    if (Fn(I, VT, RegVT, CCValAssign::Full, Flags, this->CCInfo)) {
      LLVM_DEBUG(dbgs() << "Call result #" << I << " has unhandled type "
                 << EVT(VT).getEVTString() << '\n');
      llvm_unreachable(nullptr);
    }
...

次にReturn命令の扱い方だが、これもMYRISCVXRetノードを使用して、ある条件下の下で別のノードを生成するようなルールを追加する。 MYRISCVXInstrInfo.tdに以下を追加した。 Return文(関数の戻り)の際にこの命令が生成される。ただし実装から分かるようにこの命令は疑似命令で、内部はMYRISCVXRetとなる。

f:id:msyksphinz:20190601165006p:plain
Retノードから関数戻り時に使用するRetRAを生成する仕組み
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
let isReturn=1, isTerminator=1, hasDelaySlot=0, isBarrier=1, hasCtrlDep=1 in
  def RetRA : MYRISCVXPseudo<(outs), (ins), "", [(MYRISCVXRet)]>;
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrFormats.td
//===-------------------------------===//
// MYRISCVX Pseudo Instructions Format
//===-------------------------------===//
class MYRISCVXPseudo<dag outs, dag ins, string asmstr, list<dag> pattern>:
      MYRISCVXInst<outs, ins, asmstr, pattern, IIPseudo, Pseudo> {
  let isCodeGenOnly = 1;
  let isPseudo = 1;
}

そして、RetRAMYRISCVXSEInstrInfo.cppで展開の方法が定義される。 つまり、expandPostRAPseudoによってRetRAが指定されると、expandRetRAによって当該疑似命令を引数RAレジスタMYRISCVX::Ret命令に変換する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXSEInstrInfo.cpp
//@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:
    expandRetRA(MBB, MI);
    break;
  }

  MBB.erase(MI);
  return true;
}


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