FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (30. 関数コールのサポート: LowerFormalArgumentsの実装)

関数の呼び出し規約が決まったので、関数呼び出しを含むLLVM IRを処理するフェーズに入る。 まず、LLVM IRをDAGに変換する。 LLVM IRをDAGに変換するのはMYRISCVXTaregtLowerigの仕事だ。 その中で引数の解析とDAG変換を行うための関数はLowerFormalArguments()をオーバーライドする。 LowerFormalArguments()を実装してみる。

関数の定義にあたり、2つのISELLoweringの関数を実装する必要がある。

  • LowerFormalArguments() : 引数をDAGに変換する。呼び出し規約に基づいて引数の位置を決定する。つまり、
    • スタック経由で渡す引数 : フレームインデックスとスタックロード命令を定義する。
    • レジスタ経由で渡す引数 : CopyFromRegノードを生成する。
  • LowerReturn() : 戻り値の処理をDAGに変換する。
    • 戻り値を探索し、それに基づいてMYRISCVXISD::Retノードを生成する。

LowerFormalArguments()の解析をする。

MYRISCVXTargetLowering::LowerFormalArguments (SDValue Chain,
                                              CallingConv::ID CallConv,
                                              bool IsVarArg,
                                              const SmallVectorImpl<ISD::InputArg> &Ins,
                                              const SDLoc &DL, SelectionDAG &DAG,
                                              SmallVectorImpl<SDValue> &InVals) const {

...
   Function::const_arg_iterator FuncArg =
      DAG.getMachineFunction().getFunction().arg_begin();
...
   CCInfo.AnalyzeFormalArguments (Ins, CC_MYRISCVX);
...

CCInfo.AnalyzeFormalArguments()で引数の解析を行っている。解析というのはつまり、関数の引数に対して

  • この引数はレジスタ渡しをするのか。どのレジスタを使うのか。
  • この引数はスタック渡しをするのか。スタックのアドレスは何処を使うのか。

と言う事を決定する。すべての引数についてこの情報を決めるための処理だが、実際にはAnalyzeFormalArgumentsの引数として指定したCC_MYRISCVXを呼び出している。

このCC_MYRISCVXがまさに、先ほどtdファイルに記述した関数呼び出し規約のルールを記載した関数に他なりません。これはbuild-myriscvx/lib/Target/MYRISCVX/MYRISCVXGenCallingConv.incに生成されている。

  • build-myriscvx/lib/Target/MYRISCVX/MYRISCVXGenCallingConv.inc
static bool CC_MYRISCVX(unsigned ValNo, MVT ValVT,
                        MVT LocVT, CCValAssign::LocInfo LocInfo,
                        ISD::ArgFlagsTy ArgFlags, CCState &State) {

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

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

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

上記で説明した通り、CC_STACK32CC_LP32が条件分岐によって使い分けられている。

static bool CC_LP32(unsigned ValNo, MVT ValVT,
                    MVT LocVT, CCValAssign::LocInfo LocInfo,
                    ISD::ArgFlagsTy ArgFlags, CCState &State) {
...

詳細はここでは掲載しないが、上記のLP32のルールと全く同じことを計算している。AnalyzeFormalArguments()の引数として渡されたCCStateが情報を記憶しておき、一つ一つの引数に対してレジスタの割り当て、もしくはスタックの割り当てを行っている。

STACK32も同様だ。こちらには、レジスタ割り当てに関する記述が入っている。

static bool CC_STACK32(unsigned ValNo, MVT ValVT,
                       MVT LocVT, CCValAssign::LocInfo LocInfo,
                       ISD::ArgFlagsTy ArgFlags, CCState &State) {
...

引数の解析が終了すると、次にDAGの生成に入る。引数の数だけループが実行されます。

  for (unsigned i = 0, e = ArgLocs.size(); i != e; ++i) {
...

レジスタ渡しの場合はあまりやることは少ないのだが、一点ほど、32ビットよりも小さな値を渡しているときに限り、呼び出し規約のルールで型の拡張を行っていました。この時には、AssertSextもしくはAssertZextノードを挿入する必要がある。このノードは2つのオペランドを持っており、1つは拡張された値そのもの、そしてもう一つは拡張前の値のビットサイズだ。

      // If this is an 8 or 16-bit value, it has been passed promoted
      // to 32 bits.  Insert an assert[sz]ext to capture this, then
      // truncate to the right size.
      if (VA.getLocInfo() != CCValAssign::Full) {
        unsigned Opcode = 0;
        if (VA.getLocInfo() == CCValAssign::SExt)
          Opcode = ISD::AssertSext;
        else if (VA.getLocInfo() == CCValAssign::ZExt)
          Opcode = ISD::AssertZext;
        if (Opcode)
          ArgValue = DAG.getNode(Opcode, DL, RegVT, ArgValue,
                                 DAG.getValueType(ValVT));
        ArgValue = DAG.getNode(ISD::TRUNCATE, DL, ValVT, ArgValue);
      }

一方、スタック渡しの場合はスタックフレームから当該引数が格納されているスタックまでの距離を計算し、それに基づいてメモリロードのノードを生成する。

      MVT LocVT = VA.getLocVT();

      // sanity check
      assert(VA.isMemLoc());

      // The stack pointer offset is relative to the caller stack frame.
      int FI = MFI.CreateFixedObject(ValVT.getSizeInBits()/8,
                                     VA.getLocMemOffset(), true);

      // Create load nodes to retrieve arguments from the stack
      SDValue FIN = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
      SDValue Load = DAG.getLoad(
          LocVT, DL, Chain, FIN,
          MachinePointerInfo::getFixedStack(DAG.getMachineFunction(), FI));
      InVals.push_back(Load);
      OutChains.push_back(Load.getValue(1));
f:id:msyksphinz:20190625225902p:plain
MYRISCVXの関数引数を渡すための2つのABI

念のため、ILP32のレジスタ渡し出来る数を1に限定して、以下のプログラムでDAGを作ってみた。

int sum_i(int x1, int x2)
{
  int sum = x1 + x2;

  return sum;
}

ILP32とSTACK32でDAGを作ってみる。赤い四角がLoad命令、青い四角がCopyToRegだ。ILP32では最初の引数がレジスタ渡しで、STACK32ではすべてスタック渡しとなっていることが分かる。

f:id:msyksphinz:20190626003612p:plain