関数の呼び出し規約が決まったので、関数呼び出しを含む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_STACK32とCC_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));

念のため、ILP32のレジスタ渡し出来る数を1に限定して、以下のプログラムでDAGを作ってみた。
int sum_i(int x1, int x2) { int sum = x1 + x2; return sum; }
ILP32とSTACK32でDAGを作ってみる。赤い四角がLoad命令、青い四角がCopyToRegだ。ILP32では最初の引数がレジスタ渡しで、STACK32ではすべてスタック渡しとなっていることが分かる。