FPGA開発日記

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

RISC-V 64-bit LLVM Backendを試す (11. 簡単なReturn命令を生成する)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。 簡単なReturn IRが変換できるように実装を進めていこう。

実装するのに見ている関数群は以下のようなものだが、まず関数の意味が分からない、というところから始めないといけない。

  • LowerFormalArguments(): 入力引数を処理するためのフック。
  • LowerReturn() : return値を処理するためのフック。値をreturn用レジスタ(RISC-Vの場合はA0)に挿入する。

CCValAssign : 戻り値が戻り値用のレジスタに格納されたことを表現する。

SmallVector<CCValAssign, 16> RVLocs;

analyzeReturnにより戻り値の解析を行う、らしい。

  // Analyze return values.
  MYRISCVXCCInfo.analyzeReturn(Outs, Subtarget.abiUsesSoftFloat(),
                               MF.getFunction().getReturnType());

実体はこちら。

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;

  for (unsigned I = 0, E = RetVals.size(); I < E; ++I) {
    MVT VT = RetVals[I].VT;
    ISD::ArgFlagsTy Flags = RetVals[I].Flags;
    MVT RegVT = this->getRegVT(VT, RetTy, CallNode, IsSoftFloat);

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

もう単語の意味から型の意味まで全く分からないのだが、

  • MVT = Machine Value Type : 型の種類のようなもの。
    if (Fn(I, VT, RegVT, CCValAssign::Full, Flags, this->CCInfo)) {

の意味しているところは、つまるところRetCC_MYRISCVX(I, VT, RegVT, CCValAssign::Full, Flags, this->CCInfo)を呼び出して、 その型にマッチしていなければFalseを返すということだ。これでReturnする値の型を判定している?

  // Copy the result values into the output registers.
  for (unsigned i = 0; i != RVLocs.size(); ++i) {
    SDValue Val = OutVals[i];
    CCValAssign &VA = RVLocs[i];
    assert(VA.isRegLoc() && "Can only return in registers!");

    if (RVLocs[i].getValVT() != RVLocs[i].getLocVT())
      Val = DAG.getNode(ISD::BITCAST, DL, RVLocs[i].getLocVT(), Val);

    Chain = DAG.getCopyToReg(Chain, DL, VA.getLocReg(), Val, Flag);

    // Guarantee that all emitted copies are stuck together with flags.
    Flag = Chain.getValue(1);
    RetOps.push_back(DAG.getRegister(VA.getLocReg(), VA.getLocVT()));
  }

SmallVectorのサイズがなぜ16なのかは分からない。おそらく16個以上の戻り値を返そうとすると(C言語では不可能だがほかの言語だと可能かもしれない)、 エラーを吐いてしまうのではなかろうか。その辺りの実装はどのようになっているのかはよく分からない。 forループの中にprintf()を入れて確認してみるとこのループは1度しか実行されていないようだった。

チュートリアル通り、-O2で最適化したIRで生成すると、A0に戻り値を渡してからRetが実行されているのが分かる。 -O2無しで生成したIRでは、まだクラッシュしてしまう。

./bin/clang -O2 -c -target mips-unknown-linux-gnu ../lbdex/input/ch3.cpp -emit-llvm -o ch3.myriscvx_o2.bc
./bin/llc -march=myriscvx -relocation-model=pic -filetype=asm ch3.myriscvx_o2.bc -o -

...

# %bb.0:                                # %entry
        addiu   $x10, $x0, 0
        ret     $x1

Small Immediateの挿入

MYRISCVXInstrInfo.tdに以下のパターンを追加しており、これで"return val(val は即値)" のA0レジスタへの代入処理が実行できるようになっている。

//===----------------------------------------------------------------------===//
//  Arbitrary patterns that map to one or more instructions
//===----------------------------------------------------------------------===//

// Small immediates
def : Pat<(i32 immSExt16:$in),
          (ADDiu ZERO, imm:$in)>;
f:id:msyksphinz:20190117013715p:plain
return命令の生成結果