FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (33. 関数コールに関するLLVM IRをDAGに変換する)

f:id:msyksphinz:20190425001356p:plain

関数コールに関するLLVM IRをDAGに変換するためには、MYRISCVXTaregtLowering::LowerCallを実装する。LowerCallの役割を理解するために、LowerCallのコメントを読んでみる。

  • llvm-myriscvx/include/llvm/CodeGen/TargetLowering.h
  /// This hook must be implemented to lower calls into the specified
  /// DAG. The outgoing arguments to the call are described by the Outs array,
  /// and the values to be returned by the call are described by the Ins
  /// array. The implementation should fill in the InVals array with legal-type
  /// return values from the call, and return the resulting token chain value.
  /// このフックは関数コールを特定のDAGに変換するために実装する必要がある。
  /// 関数に渡される引数はOuts配列に記述されており、関数から戻される値はIns配列に記述されている。
  /// 実装では、InVals配列に関数からの正しい型の戻り値を埋める必要があり、トークンチェインの値を戻す必要がある。
  virtual SDValue
    LowerCall(CallLoweringInfo &/*CLI*/,
              SmallVectorImpl<SDValue> &/*InVals*/) const {
    llvm_unreachable("Not Implemented");
  }

LowerCallの中で実装しなければならないことは、具体的には以下になる。

  1. 引数を解析し、レジスタ経由で渡すかスタック経由で渡すかなどの情報を決定する。
  2. 全ての引数について、レジスタ経由渡しの場合は引数のレジスタコピーのDAGを生成する。スタック渡しの場合は適切なスタックスロットに値をコピーするDAGを生成する。
  3. 呼び出し先関数の場所を取得するためのDAGを生成する。呼び出し先の関数アドレスに対してMYRISCVXISD::CALLノードを生成し、関数へのジャンプするDAGを生成する。
  4. 戻り値を扱うためのDAGを生成する。これには、LowerCallResultという別の関数を定義する。
f:id:msyksphinz:20190630142009p:plain
LowerCallとLowerCallResultで実施される処理の概要

1. 引数の解析

引数の解析はLowerFormalArgumentsでも取り上げた。LowerFormalArgumentsでは引数毎にレジスタから取るか、スタックから取るかを解析したが、LowerCallの場合はその逆で、引数毎にレジスタに値を置くか、スタックに置くかを解析する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
SDValue
MYRISCVXTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
                                  SmallVectorImpl<SDValue> &InVals) const {
...
  // Analyze operands of the call, assigning locations to each operand.
  SmallVector<CCValAssign, 16> ArgLocs;
  CCState CCInfo(CallConv, IsVarArg, DAG.getMachineFunction(),
                 ArgLocs, *DAG.getContext());
  CCInfo.AnalyzeCallOperands (Outs, CC_MYRISCVX);
...

CCInfoの中に、引数の渡し方についての情報が格納された。これを使って引数渡しのためのDAGを構築していくのだが、その前にまずは関数コールのためのお膳立てをする。

2. 引数渡し用のDAGの生成

まず、CALLSEQ_STARTDAGノードを生成して、このノードから関数コールのためのDAGが始まるということを宣言する。

Chain = DAG.getCALLSEQ_START(Chain, NextStackOffset, 0, DL);

ここから先は、引数渡しのためのDAGを作っていく。そのために、引数の数だけループを回する。

  for (unsigned i = 0, e = ArgLocs.size(); i != e; ++i) {
    SDValue Arg = OutVals[i];
    CCValAssign &VA = ArgLocs[i];
    MVT LocVT = VA.getLocVT();
    ISD::ArgFlagsTy Flags = Outs[i].Flags;
...
    // Arguments that can be passed on register must be kept at
    // RegsToPass vector
    if (VA.isRegLoc()) {
      RegsToPass.push_back(std::make_pair(VA.getLocReg(), Arg));
      continue;
    }

レジスタ渡しの引数の場合は、RegsToPassに引数の情報を詰め込んでいく。RegsToPassstd::pair<unsigned, SDValue>のキューになっており、引数私に使用されるレジスタの番号と引数そのものをペアで格納していく。

    // Arguments that can be passed on register must be kept at
    // RegsToPass vector
    if (VA.isRegLoc()) {
      RegsToPass.push_back(std::make_pair(VA.getLocReg(), Arg));
      continue;
    }

スタック渡しの引数の場合もMemOpChainsに引数を積み上げていく。passArgOnStack()は、具体的にはスタックポインタのアドレス計算のためのDAGを生成して、それを元にストア命令のためのDAGを生成する。

    // Register can't get to this point...
    assert(VA.isMemLoc());

    // emit ISD::STORE whichs stores the
    // parameter value to a stack Location
    MemOpChains.push_back(passArgOnStack(StackPtr, VA.getLocMemOffset(),
                                         Chain, Arg, DL, IsTailCall, DAG));
SDValue
MYRISCVXTargetLowering::passArgOnStack(SDValue StackPtr, unsigned Offset,
                                       SDValue Chain, SDValue Arg, const SDLoc &DL,
                                       bool IsTailCall, SelectionDAG &DAG) const {
    SDValue PtrOff =
        DAG.getNode(ISD::ADD, DL, getPointerTy(DAG.getDataLayout()), StackPtr,
                    DAG.getIntPtrConstant(Offset, DL));
    return DAG.getStore(Chain, DL, Arg, PtrOff, MachinePointerInfo());
}

上記の処理を引数の数だけ繰り返する。スタック経由でのアクセスが発生している場合は、すべてのストア命令を1つの単一ノードに変化してDAGに接続する。

  // Transform all store nodes into one single node because all store
  // nodes are independent of each other.
  if (!MemOpChains.empty())
    Chain = DAG.getNode(ISD::TokenFactor, DL, MVT::Other, MemOpChains);