FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (34. 関数コールと戻り値に関するDAGを生成する)

f:id:msyksphinz:20190425001356p:plain

3. 関数呼び出しのDAG生成

次に、ジャンプ先となる関数のアドレスを計算する。PICモードでコンパイルしている場合はGOT経由でのアドレスを計算、そうでない場合はっ直接アドレスの計算を行う。これをGlobalAddressSDNode, ExternalSymbolSDNodeのタイプに対してそれぞれ行う。

  if (GlobalAddressSDNode *G = dyn_cast<GlobalAddressSDNode>(Callee)) {
    if (IsPICCall) {
      const GlobalValue *Val = G->getGlobal();
      InternalLinkage = Val->hasInternalLinkage();

      if (InternalLinkage)
        Callee = getAddrLocal(G, Ty, DAG);
      else
        Callee = getAddrGlobal(G, Ty, DAG, MYRISCVXII::MO_GOT_CALL, Chain,
                               FuncInfo->callPtrInfo(Val));
    } else
      Callee = DAG.getTargetGlobalAddress(G->getGlobal(), DL,
                                          getPointerTy(DAG.getDataLayout()), 0,
                                          MYRISCVXII::MO_NO_FLAG);
    GlobalOrExternal = true;
  } else if (ExternalSymbolSDNode *S = dyn_cast<ExternalSymbolSDNode>(Callee)) {
    const char *Sym = S->getSymbol();

    if (!IsPIC) // static
      Callee = DAG.getTargetExternalSymbol(Sym,
                                           getPointerTy(DAG.getDataLayout()),
                                           MYRISCVXII::MO_NO_FLAG);
    else // PIC
      Callee = getAddrGlobal(S, Ty, DAG, MYRISCVXII::MO_GOT_CALL, Chain,
                             FuncInfo->callPtrInfo(Sym));

    GlobalOrExternal = true;
  }

最後に、MYRISCVXISD::CALLノードを接続して、実際に関数ジャンプを行うノードをDAGに接続する。

  Chain = DAG.getNode(MYRISCVXISD::CALL, DL, NodeTys, Ops);

最初に、CALLSEQ_STARTノードを挿入したので、ここまでで関数ジャンプのDAGが終わりであることを示すCALLSEQ_ENDノードを挿入する。

  // Create the CALLSEQ_END node.
  Chain = DAG.getCALLSEQ_END(Chain, NextStackOffsetVal,
                             DAG.getIntPtrConstant(0, DL, true), InFlag, DL);

4. 戻り値取得のDAG生成

戻り値の処理だが、LowerCallResult関数を作成してここだけ別の関数に切り出する。LowerCallResultで行わなければならないのは関数から戻ってきたときの戻り値を受け取ってしかるべき場所に格納するという処理だ。

/// LowerCallResult - Lower the result values of a call into the
/// appropriate copies out of appropriate physical registers.
SDValue
MYRISCVXTargetLowering::LowerCallResult(SDValue Chain, SDValue InFlag,
                                        CallingConv::ID CallConv, bool IsVarArg,
                                        const SmallVectorImpl<ISD::InputArg> &Ins,
                                        const SDLoc &DL, SelectionDAG &DAG,
                                        SmallVectorImpl<SDValue> &InVals,
                                        const SDNode *CallNode,
                                        const Type *RetTy) const {
...
  // Assign locations to each value returned by this call.
  SmallVector<CCValAssign, 16> RVLocs;
  CCState CCInfo(CallConv, IsVarArg, DAG.getMachineFunction(),
                 RVLocs, *DAG.getContext());
  const ExternalSymbolSDNode *ES =
      dyn_cast_or_null<const ExternalSymbolSDNode>(CallNode);
  CCInfo.AnalyzeCallResult(Ins, RetCC_MYRISCVX);
 ...

AnalyzeCallResultを呼んで、関数の戻り値を格納するレジスタの解析を行う。AnalyzeFormalArguments()と構成は似ており、呼び出し規約RetCC_MYRISCVXを呼び出して解析に使用している。念のためRetCC_MYRISCVXの中身を確認してみると、RetCC_MYRISCVXEABIを呼び出しており、RetCC_MYRISCVXEABIでは戻り値レジスタとしてA0とA1レジスタを使用することが明記されている。

  • llvm-myriscvx/lib/CodeGen/CallingConvLower.cpp
/// Analyze the return values of a call, incorporating info about the passed
/// values into this state.
void CCState::AnalyzeCallResult(const SmallVectorImpl<ISD::InputArg> &Ins,
                                CCAssignFn Fn) {
  for (unsigned i = 0, e = Ins.size(); i != e; ++i) {
    MVT VT = Ins[i].VT;
    ISD::ArgFlagsTy Flags = Ins[i].Flags;
    if (Fn(i, VT, VT, CCValAssign::Full, Flags, *this)) {
#ifndef NDEBUG
      dbgs() << "Call result #" << i << " has unhandled type "
             << EVT(VT).getEVTString() << '\n';
#endif
      llvm_unreachable(nullptr);
    }
  }
}
  • build-myriscvx/lib/Target/MYRISCVX/MYRISCVXGenCallingConv.inc
static bool RetCC_MYRISCVXEABI(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.
}

解析後、すべての戻り値(といってもC言語の場合は戻り値は1つだけだが)において、DAGを生成する。getCopyFromReg()のDAGを生成して物理レジスタからノードにデータをコピーして、その後のDAGに接続できるようにしている。RVLocs[i].getValVT() != RVLocs[i].getLocVT()は、格納しているレジスタの場所とデータの実体の型が合っていない場合に、BITCASTノードを接続して型の拡張を行うためのDAGだ。

最終的に、生成されたDAGは以下のようになった。それぞれのステップで挿入したノードに、赤い四角を書いている。

f:id:msyksphinz:20190630142116p:plain
関数コールのために生成されたDAG