FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

オリジナルLLVMバックエンド実装をまとめる(4. `ISelLowering`の初期実装とCalling Conventionの追加)

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

f:id:msyksphinz:20190918013437p:plain

まずどのような変換処理から実装していけばよいだろうか?ここでは関数が呼ばれてから終了するまでの処理(main関数)を変換するわけだから、関数の呼び出しの変換処理、関数から戻るための変換処理を追加する必要がありそうだ。

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

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

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

f:id:msyksphinz:20190918013606p:plain
LowerFormalArgumentsとLowerReturnの処理される場所
ファイル名 関数名 説明
MYRISCVXISelLowering.cpp LowerFormalArguments() ISelLoweringの処理において、関数の先頭で引数を処理を行う。
MYRISCVXISelLowering.cpp LowerReturn() ISelLoweringの処理において、関数の戻り値に関する処理を行う。
  • llvm-myriscvx80/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));
}

とりあえず空っぽだ。LowerFormalArgumentsに関しては何もせず、LowerReturnに関しては、Retノード命令を使ってSelectionDAGへと変換していることが分かる。

ただし、このままではLowerFormalArguments()LowerReturn()は正しく動作しない。また、MIPSなどと同様にMYRISCVXにもCalling Conventionが存在し、引数を渡す際にも特定のレジスタを使用する必要がある。

MYRISCVXの場合はRISC-Vと同様に整数の引数は汎用レジスタA0-A7に格納して渡すモードもあるのだが、開設のためにもう一つ、すべての引数をスタック経由で渡すモードも用意している。

それぞれのこの関数呼び出しの規約をLP32, STACK32(32-bit版), LP64, STACK64(64-bit版)と呼ぶ。

Calling Convention名 説明
LP32 / LP64 最初の8個の引数は、A0-A7に格納し、それ以降はスタック経由で引数を渡す。
STACK32 / STACK64 すべての引数をスタック経由で渡す。

このCalling Conventionを実現するのがMYRISCVXCallingConv.tdに記述していく。これらを制御するために、CCIfTypeおよびCCAssignToRegを使って制御する。

まずは、簡単そうな戻り値に関するCalling Conventionの実装から始める。

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

commit:84bc7aa2b1b Add MYRISCVX Calling Convention LP32/STACK32/LP64/STACK64

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXCallingConv.td
//===----------------------------------------------------------------------===//
// MYRISCVX LP32/STACK32 Return Convention
//===----------------------------------------------------------------------===//

def RetCC_LP32 : CallingConv<[
  CCIfType<[i32], CCAssignToReg<[A0, A1]>>
]>;

def RetCC_STACK32 : CallingConv<[
  CCIfType<[i32], CCAssignToStack<4, 4>>
]>;


def RetCC_MYRISCVX32 : CallingConv<[
  CCIfSubtarget<"isABI_STACK()", CCDelegateTo<RetCC_STACK32>>,
  CCDelegateTo<RetCC_LP32>
]>;


//===----------------------------------------------------------------------===//
// MYRISCVX LP64/STACK64 Return Convention
//===----------------------------------------------------------------------===//

def RetCC_LP64 : CallingConv<[
  CCIfType<[i64], CCAssignToReg<[A0, A1]>>
]>;

def RetCC_STACK64 : CallingConv<[
  CCIfType<[i64], CCAssignToStack<8, 8>>
]>;

def RetCC_MYRISCVX64 : CallingConv<[
  CCIfSubtarget<"isABI_STACK()", CCDelegateTo<RetCC_STACK64>>,
  CCDelegateTo<RetCC_LP64>
]>;

このRetCC_MYRISCVX32/RetCC_MYRISCVX64はInstruction Selection時のAnalyzeReturnで使用される。AnalyzeReturnLowerReturnから呼び出される。

それぞれの中身は、見てみると簡単だ。LP32/LP64のCalling Conventionの場合は戻り値をレジスタA0, A1に割り当てます。これをCCAssignToRegという命令によって表現している。また、STACK32/STACK64のCalling Conventionの場合は、CCAssignToStackという命令を使用してスタックに割り当てます。文法はCCAssignToStack<Size, Align>となっており、最初の数字はデータサイズ、次の数字はアドレスアラインを示している。

そして、LP64/STACK64, LP32/STACK32をそれぞれオプションで切り替えるための、大元となるRetCC_MYRISCVX64, RetCC_MYRISCVX32を定義している。CCIfSubTargetという命令を独自に追加し、サブターゲット内の条件によってCalling Conventionを切り替えるようにしている。

/// CCIfSubtarget - Match if the current subtarget has a feature F.
class CCIfSubtarget<string F, CCAction A, string Invert = "">
    : CCIf<!strconcat(Invert,
                      "static_cast<const MYRISCVXSubtarget&>"
            "(State.getMachineFunction().getSubtarget()).",
                      F),
           A>;

ちなみに、生成されたMYRISCVXGenCallingConv.incの一部を切り取る。このように、CCIfSubTargetによりサブターゲットの条件分岐が行われ、さらにCC_LP{32,64}, CC_STACK{32, 64}の中で引数のチェックが行われている。

  • build-myriscvx80/lib/Target/MYRISCVX/MYRISCVXGenCallingConv.inc
// おおもとのCalling Convention。内部でCC_STACK32とCC_LP32を条件分岐で使い分ける。
static bool RetCC_MYRISCVX32(unsigned ValNo, MVT ValVT,
                             MVT LocVT, CCValAssign::LocInfo LocInfo,
                             ISD::ArgFlagsTy ArgFlags, CCState &State) {

  if (static_cast<const MYRISCVXSubtarget&>(State.getMachineFunction().getSubtarget()).isABI_STACK()) {
    if (!RetCC_STACK32(ValNo, ValVT, LocVT, LocInfo, ArgFlags, State))
      return false;
  }

  if (!RetCC_LP32(ValNo, ValVT, LocVT, LocInfo, ArgFlags, State))
    return false;

  return true;  // CC didn't match.
}

// RetCC_STACK32の実装。
// スタックのアロケーションを行っている。
static bool RetCC_STACK32(unsigned ValNo, MVT ValVT,
                          MVT LocVT, CCValAssign::LocInfo LocInfo,
                          ISD::ArgFlagsTy ArgFlags, CCState &State) {

  if (LocVT == MVT::i32) {
    unsigned Offset1 = State.AllocateStack(4, 4);
    State.addLoc(CCValAssign::getMem(ValNo, ValVT, Offset1, LocVT, LocInfo));
    return false;
  }

  return true;  // CC didn't match.
}


// RetCC_LP32の実装
static bool RetCC_LP32(unsigned ValNo, MVT ValVT,
                       MVT LocVT, CCValAssign::LocInfo LocInfo,
                       ISD::ArgFlagsTy ArgFlags, CCState &State) {

  if (LocVT == MVT::i32) {
    static const MCPhysReg RegList1[] = {
      MYRISCVX::A0, MYRISCVX::A1
    };
    if (unsigned Reg = State.AllocateReg(RegList1)) {
      State.addLoc(CCValAssign::getReg(ValNo, ValVT, Reg, LocVT, LocInfo));
      return false;
    }
  }

  return true;  // CC didn't match.
}