関数の呼び出し規約が決まったので、関数呼び出しを含む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ではすべてスタック渡しとなっていることが分かる。