関数コールに関する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
の中で実装しなければならないことは、具体的には以下になる。
- 引数を解析し、レジスタ経由で渡すかスタック経由で渡すかなどの情報を決定する。
- 全ての引数について、レジスタ経由渡しの場合は引数のレジスタコピーのDAGを生成する。スタック渡しの場合は適切なスタックスロットに値をコピーするDAGを生成する。
- 呼び出し先関数の場所を取得するためのDAGを生成する。呼び出し先の関数アドレスに対して
MYRISCVXISD::CALL
ノードを生成し、関数へのジャンプするDAGを生成する。 - 戻り値を扱うためのDAGを生成する。これには、
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_START
DAGノードを生成して、このノードから関数コールのための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
に引数の情報を詰め込んでいく。RegsToPass
はstd::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);