MYRISCVXISelLowering
はLLVM IRからSelectionDAG(データフローグラフ)への変換プロセスになる。バックエンドのかなり初期の部分で適用されます。
まずどのような変換処理から実装していけばよいだろうか?ここでは関数が呼ばれてから終了するまでの処理(main
関数)を変換するわけだから、関数の呼び出しの変換処理、関数から戻るための変換処理を追加する必要がありそうだ。
ここで必要な実装はMYRISCVXISelLowering.cpp
を実装する必要がある。
関数の呼び出し処理とは、つまり引数の処理だ。関数コールを行った側から引数を正しく受け取り、関数内で使えるようにする。このための処理はLowerFormalArguments()
に実装する。
一方で、関数から戻る処理は、戻り値を所定の場所に格納し、戻り場所までジャンプする。このための処理はLowerReturn
に実装する。
ファイル名 | 関数名 | 説明 |
---|---|---|
MYRISCVXISelLowering.cpp |
LowerFormalArguments() |
ISelLoweringの処理において、関数の先頭で引数を処理を行う。 |
MYRISCVXISelLowering.cpp |
LowerReturn() |
ISelLoweringの処理において、関数の戻り値に関する処理を行う。 |
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
//@LowerFormalArguments { /// LowerFormalArguments - transform physical registers into virtual registers /// and generate load operations for arguments places on the stack. SDValue MYRISCVXTargetLowering::LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL, SelectionDAG &DAG, SmallVectorImpl<SDValue> &InVals) const { return Chain; } // @LowerFormalArguments } //===----------------------------------------------------------------------===// //@ Return Value Calling Convention Implementation //===----------------------------------------------------------------------===// SDValue MYRISCVXTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, const SmallVectorImpl<ISD::OutputArg> &Outs, const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL, SelectionDAG &DAG) const { return DAG.getNode(MYRISCVXISD::Ret, DL, MVT::Other, Chain, DAG.getRegister(MYRISCVX::RA, MVT::i32)); }
とりあえず空っぽだ。LowerFormalArguments
に関しては何もせず、LowerReturn
に関しては、Ret
ノード命令を使ってSelectionDAGへと変換していることが分かる。
ただし、このままではLowerFormalArguments()
とLowerReturn()
は正しく動作しない。また、MIPSなどと同様にMYRISCVXにもCalling Conventionが存在し、引数を渡す際にも特定のレジスタを使用する必要がある。
MYRISCVXの場合はRISC-Vと同様に整数の引数は汎用レジスタA0-A7に格納して渡すモードもあるのだが、開設のためにもう一つ、すべての引数をスタック経由で渡すモードも用意している。
それぞれのこの関数呼び出しの規約をLP32, STACK32(32-bit版), LP64, STACK64(64-bit版)と呼ぶ。
Calling Convention名 | 説明 | |
---|---|---|
LP32 / LP64 | 最初の8個の引数は、A0-A7に格納し、それ以降はスタック経由で引数を渡す。 | |
STACK32 / STACK64 | すべての引数をスタック経由で渡す。 | |
このCalling Conventionを実現するのがMYRISCVXCallingConv.td
に記述していく。これらを制御するために、CCIfType
およびCCAssignToReg
を使って制御する。
まずは、簡単そうな戻り値に関するCalling Conventionの実装から始める。
以下の記述では、関数の戻り値が渡される場合に、CCAssignToReg
で示されるレジスタのどれかに引数が格納されるルールが追加されている。そしてCalling Conventionである`RetCC_MYRISCVX32/RetCC_MYRISCVX64を定義する。
commit:84bc7aa2b1b Add MYRISCVX Calling Convention LP32/STACK32/LP64/STACK64
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXCallingConv.td
//===----------------------------------------------------------------------===// // MYRISCVX LP32/STACK32 Return Convention //===----------------------------------------------------------------------===// def RetCC_LP32 : CallingConv<[ CCIfType<[i32], CCAssignToReg<[A0, A1]>> ]>; def RetCC_STACK32 : CallingConv<[ CCIfType<[i32], CCAssignToStack<4, 4>> ]>; def RetCC_MYRISCVX32 : CallingConv<[ CCIfSubtarget<"isABI_STACK()", CCDelegateTo<RetCC_STACK32>>, CCDelegateTo<RetCC_LP32> ]>; //===----------------------------------------------------------------------===// // MYRISCVX LP64/STACK64 Return Convention //===----------------------------------------------------------------------===// def RetCC_LP64 : CallingConv<[ CCIfType<[i64], CCAssignToReg<[A0, A1]>> ]>; def RetCC_STACK64 : CallingConv<[ CCIfType<[i64], CCAssignToStack<8, 8>> ]>; def RetCC_MYRISCVX64 : CallingConv<[ CCIfSubtarget<"isABI_STACK()", CCDelegateTo<RetCC_STACK64>>, CCDelegateTo<RetCC_LP64> ]>;
このRetCC_MYRISCVX32/RetCC_MYRISCVX64
はInstruction Selection時のAnalyzeReturn
で使用される。AnalyzeReturn
はLowerReturn
から呼び出される。
それぞれの中身は、見てみると簡単だ。LP32/LP64のCalling Conventionの場合は戻り値をレジスタA0, A1に割り当てます。これをCCAssignToReg
という命令によって表現している。また、STACK32/STACK64のCalling Conventionの場合は、CCAssignToStack
という命令を使用してスタックに割り当てます。文法はCCAssignToStack<Size, Align>
となっており、最初の数字はデータサイズ、次の数字はアドレスアラインを示している。
そして、LP64/STACK64, LP32/STACK32をそれぞれオプションで切り替えるための、大元となるRetCC_MYRISCVX64, RetCC_MYRISCVX32
を定義している。CCIfSubTarget
という命令を独自に追加し、サブターゲット内の条件によってCalling Conventionを切り替えるようにしている。
/// CCIfSubtarget - Match if the current subtarget has a feature F. class CCIfSubtarget<string F, CCAction A, string Invert = ""> : CCIf<!strconcat(Invert, "static_cast<const MYRISCVXSubtarget&>" "(State.getMachineFunction().getSubtarget()).", F), A>;
ちなみに、生成されたMYRISCVXGenCallingConv.inc
の一部を切り取る。このように、CCIfSubTarget
によりサブターゲットの条件分岐が行われ、さらにCC_LP{32,64}, CC_STACK{32, 64}
の中で引数のチェックが行われている。
build-myriscvx80/lib/Target/MYRISCVX/MYRISCVXGenCallingConv.inc
// おおもとのCalling Convention。内部でCC_STACK32とCC_LP32を条件分岐で使い分ける。 static bool RetCC_MYRISCVX32(unsigned ValNo, MVT ValVT, MVT LocVT, CCValAssign::LocInfo LocInfo, ISD::ArgFlagsTy ArgFlags, CCState &State) { if (static_cast<const MYRISCVXSubtarget&>(State.getMachineFunction().getSubtarget()).isABI_STACK()) { if (!RetCC_STACK32(ValNo, ValVT, LocVT, LocInfo, ArgFlags, State)) return false; } if (!RetCC_LP32(ValNo, ValVT, LocVT, LocInfo, ArgFlags, State)) return false; return true; // CC didn't match. } // RetCC_STACK32の実装。 // スタックのアロケーションを行っている。 static bool RetCC_STACK32(unsigned ValNo, MVT ValVT, MVT LocVT, CCValAssign::LocInfo LocInfo, ISD::ArgFlagsTy ArgFlags, CCState &State) { if (LocVT == MVT::i32) { unsigned Offset1 = State.AllocateStack(4, 4); State.addLoc(CCValAssign::getMem(ValNo, ValVT, Offset1, LocVT, LocInfo)); return false; } return true; // CC didn't match. } // RetCC_LP32の実装 static bool RetCC_LP32(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. }