命令セットアーキテクチャには、命令の定義ともに、関数呼び出しのルールを規定するCalling Conventionが定義されている。 MYRISCVXにもCalling Conventionが存在し、基本的にRISC-VのCalling Conventionに基づいている。このルールに基づき、引数を渡す際にも特定のレジスタを使用する必要がある。
呼び出し規約では、関数呼び出しの規約、つまり関数コールの際の引数をどのような形で渡すのか、戻り値はどのような形式で返すのかと言う事を規定している。 呼び出し規約はISAと直接関係ある訳ではなく、ソフトウェアによって独自に決めることができる。 しかし、呼び出し規約が異なるバイナリどうしは互いに呼び出すことができないため、ISA毎に標準的な呼び出し規約が決められているのが一般的だ。 RISC-Vでは複数の呼び出し規約が決められている。その中で、整数レジスタの動作について定義しているのはILP32だ。これは、
というルールだ。これをMYRISCVXの呼び出し規約としてそのまま採用する。
もう一つ、LLVMの動作を理解するために、独自の呼び出し規約を定義する。
- 全ての引数はスタックを経由して渡す。
- 戻り値はA0-A1レジスタを経由して呼び出し元に戻す。
というものだ。この呼び出し規約をSTACK32と呼ぶことにする。MYRISCVXでは、この2つの呼び出し規約を実装することにする。
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
まず、呼び出された関数が実行されるときのCalling Conventionから考えていく。
関数が呼び出されると、まずはCallee Savedレジスタを退避する。つまり、関数側が管理すべきレジスタをスタックに退避し、関数から戻るときにレジスタを汚してしまうことを防止する。
そして、関数内で使用する変数の分だけスタックを移動し、関数本体に入っていく。
MYRISCVXでは2種類のCalling Conventionを用意すると説明した。LP{32,64}とSTACK{32,64}だ。 LPはA0-A7までのレジスタに引数の最初の8つまでを格納し、それ以降はスタックに格納する。一方で、STACKはすべての引数をスタックに格納する。
以下の記述では、関数の引数を渡す際のルールを決めている。Calling ConventionであるCC_MYRISCVXを定義している。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXCallingConv.td
//===----------------------------------------------------------------------===// // MYRISCVX LP32/STACK32 Calling Convention //===----------------------------------------------------------------------===// def CC_LP32 : CallingConv<[ // Promote i8/i16 arguments to i32. CCIfType<[i1, i8, i16], CCPromoteToType<i32>>, // Integer arguments are passed in integer registers. CCIfType<[i32], CCAssignToReg<[A0, A1, A2, A3, A4, A5, A6, A7]>>, // Integer values get stored in stack slots that are 4 bytes in // size and 4-byte aligned. CCIfType<[i32], CCAssignToStack<4, 4>> ]>; def CC_STACK32 : CallingConv<[ // Promote i8/i16 arguments to i32. CCIfType<[i1, i8, i16], CCPromoteToType<i32>>, // Integer values get stored in stack slots that are 4 bytes in // size and 4-byte aligned. CCIfType<[i32], CCAssignToStack<4, 4>> ]>; //===----------------------------------------------------------------------===// // MYRISCVX LP64/STACK64 Calling Convention //===----------------------------------------------------------------------===// def CC_LP64 : CallingConv<[ // Promote i8/i16/i32 arguments to i64. CCIfType<[i1, i8, i16, i32], CCPromoteToType<i64>>, // Integer arguments are passed in integer registers. CCIfType<[i64], CCAssignToReg<[A0, A1, A2, A3, A4, A5, A6, A7]>>, // Integer values get stored in stack slots that are 4 bytes in // size and 4-byte aligned. CCIfType<[i64], CCAssignToStack<8, 8>> ]>; def CC_STACK64 : CallingConv<[ // Promote i8/i16/i32 arguments to i64. CCIfType<[i1, i8, i16, i32], CCPromoteToType<i64>>, // Integer values get stored in stack slots that are 8 bytes in // size and 8-byte aligned. CCIfType<[i64], CCAssignToStack<8, 8>> ]>; def CC_MYRISCVX : CallingConv<[ CCIfSubtarget<"isABI_STACK64()", CCDelegateTo<CC_STACK64>>, CCIfSubtarget<"isABI_LP64 ()", CCDelegateTo<CC_LP64>>, CCIfSubtarget<"isABI_STACK32()", CCDelegateTo<CC_STACK32>>, CCDelegateTo<CC_LP32> ]>;
まず、4種類のCalling Convention、
CC_LP32
CCL_STACK32
CC_LP64
CC_STACK64
を定義していることが分かる。CCIfType
とCCPromoteToType
を使って、XLENよりも小さな値はすべてXLENのサイズまでビット幅を拡張するように設定している。
CC_LP{32,64}
は、CCAssignToReg
によってA0-A7までのレジスタを引数わたしに使用できる。それ以降の引数はCCAssignToStack
によりスタックに割り当てる。
CC_STACK{32,64}
では、CCAssignToReg
を使用せず、すべてCCAssignToStack
によって引数をスタックに割り当てている。
さて、この4種類のCalling Conventionからどれを選ぶのか、ですがこれをひとまとめにしたCC_MYRISCVX
というCalling Conventionを定義している。
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>;
Calling Conventionの4種類の中から1種類を選択する。isABI_STACK64()
, isABI_LP64()
, isABI_STACK32()
は、それぞれMYRISCVXSubTarget.h
で定義されている。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXSubtarget.h
class MYRISCVXSubtarget : public MYRISCVXGenSubtargetInfo { virtual void anchor(); ... bool isABI_LP32() const { return getXLenVT() == MVT::i32 && getABI().IsLP(); } bool isABI_STACK32() const { return getXLenVT() == MVT::i32 && getABI().IsSTACK(); } bool isABI_LP64() const { return getXLenVT() == MVT::i64 && getABI().IsLP(); } bool isABI_STACK64() const { return getXLenVT() == MVT::i64 && getABI().IsSTACK(); } ...
それぞれ、現在のABIと現在のレジスタサイズgetXLenVT()
によりどのモードを使うのかを判定している。これにより、現在のサブターゲットの設定によりどのCalling Conventionを使うのかを1つに決定している。
関数からも戻るときのCalling Convention
次に、戻り値に関するCalling Conventionの実装から始める。
以下の記述では、関数の戻り値が渡される場合に、CCAssignToReg
で示されるレジスタのどれかに引数が格納されるルールが追加されている。そしてCalling ConventionであるRetCC_MYRISCVX
を定義する。
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>> ]>; //===----------------------------------------------------------------------===// // 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_MYRISCVX : CallingConv<[ CCIfSubtarget<"isABI_STACK64()", CCDelegateTo<RetCC_STACK64>>, CCIfSubtarget<"isABI_LP64 ()", CCDelegateTo<RetCC_LP64>>, CCIfSubtarget<"isABI_STACK32()", CCDelegateTo<RetCC_STACK32>>, CCDelegateTo<RetCC_LP32> ]>;
このRetCC_MYRISCVX
は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_MYRISCVX
を定義している。