浮動小数点演算命令を使った算術演算は生成できるようになったが、まだ関数の引数として浮動小数点型を使用することができない。 これは、MYRISCVXのCalling Conventionに、浮動小数点を追加していないからだ。
RISC-VのCalling Conventionでは、浮動小数点の関数の引数渡しは整数と同様に定義されている。
Name | ABI Mnemonic | Meaning | Preserved across calls? |
---|---|---|---|
f0-f7 |
ft0-ft7 |
Temporary registers | No |
f8-f9 |
fs0-fs1 |
Callee-saved registers | Yes* |
f10-f17 |
fa0-fa7 |
Argument registers | No |
f18-f27 |
fs2-fs11 |
Callee-saved registers | Yes* |
f28-f31 |
ft8-ft11 |
Temporary registers | No |
上記の通り、浮動小数点レジスタ経由の引数渡しでは、f10-f17
が用意されている。戻り値はf10
経由で戻せばよいだろう。
というわけで、整数レジスタのCalling Conventionと同様に、2種類のCalling Conventionを用意する。
このため、MYRISCVXCallingConv.td
に記述の追加を行う。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXCallingConv.td
LP32について、f32とf64の型についての引数の情報を指定した。
f10-f17
のレジスタを使用するように指定し(CCAssignToReg
)、残りはスタックを使用するようにする(CCAssignToStack
)。
//===----------------------------------------------------------------------===// // MYRISCVX LP32 Calling Convention //===----------------------------------------------------------------------===// def CC_LP32 : CallingConv<[ CCIfByVal<CCDelegateTo<CC_MYRISCVX_ByVal>>, // 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]>>, // Single Floating-Point arguments are passed in FP registers CCIfType<[f32], CCAssignToReg<[F10_S, F11_S, F12_S, F13_S, F14_S, F15_S, F16_S, F17_S]>>, // Double Floating-Point arguments are passed in FP registers CCIfType<[f64], CCAssignToReg<[F10_D, F11_D, F12_D, F13_D, F14_D, F15_D, F16_D, F17_D]>>, // Integer values get stored in stack slots that are 4 bytes in size and 4-byte aligned. CCIfType<[i32], CCAssignToStack<4, 4>>, // Floating-Point values get stored in stack slots that are 4 bytes in size and 4-byte aligned. CCIfType<[f32], CCAssignToStack<4, 4>>, // Floating-Point values get stored in stack slots that are 8 bytes in size and 8-byte aligned. CCIfType<[f64], CCAssignToStack<8, 8>> ]>;
STACK32については、すべての引数をレジスタで渡する。すべてCCAssignToStack
だ。
//===----------------------------------------------------------------------===// // MYRISCVX STACK32 Calling Convention //===----------------------------------------------------------------------===// def CC_STACK32 : CallingConv<[ CCIfByVal<CCDelegateTo<CC_MYRISCVX_ByVal>>, // 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>>, // Floating-Point values get stored in stack slots that are 4 bytes in size and 4-byte aligned. CCIfType<[f32], CCAssignToStack<4, 4>>, // Floating-Point values get stored in stack slots that are 8 bytes in size and 8-byte aligned. CCIfType<[f64], CCAssignToStack<8, 8>> ]>;
戻り値については、浮動小数点レジスタf10
を経由して戻する。このための記述を追加する。
def RetCC_MYRISCVXEABI : CallingConv<[ // i32 are returned in registers A0, A1 CCIfType<[i32], CCAssignToReg<[A0, A1]>>, // Floating-Point are return in registers FA0, FA1 CCIfType<[f32], CCAssignToReg<[F10_S]>>, CCIfType<[f64], CCAssignToReg<[F10_D]>> ]>;
これだけだ。LLVMをビルドして、サンプルプログラムを動作させてみる。以下のようなコードを考える。
fp_args.cpp
float test_dp_arg(float a, float b, float c) { return a + b + c; } double test_dp_arg(double a, double b, double c) { return a + b + c; } float test_dp_longarg(float f0, float f1, float f2, float f3, float f4, float f5, float f6, float f7, float f8, float f9) { return f0 + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9; } double test_dp_longarg(double f0, double f1, double f2, double f3, double f4, double f5, double f6, double f7, double f8, double f9) { return f0 + f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9; }
./bin/clang -O3 fp_args.cpp -emit-llvm ./bin/llc -filetype=asm fp_args.bc -mcpu=simple32 -march=myriscvx32 -target-abi=lp64 -relocation-model=static -o -
- LP32の場合。
test_dp_arg()
のコンパイル結果。float
の場合は以下のようなコードとなる。
_Z11test_dp_argfff: # %bb.0: # %entry fadd.s f0, f10, f11 fadd.s f10, f0, f12 ret
単純に引数渡しで演算をおこない、レジスタ経由で戻り値を渡する。
test_dp_longarg()
のコンパイル結果。float
の場合は以下のようなコードとなる。
_Z15test_dp_longargdddddddddd: # %bb.0: # %entry fadd.d f0, f10, f11 fadd.d f0, f0, f12 fadd.d f0, f0, f13 fadd.d f0, f0, f14 fadd.d f0, f0, f15 fadd.d f0, f0, f16 fadd.d f0, f0, f17 fld f1, 0(x2) fadd.d f0, f0, f1 fld f1, 8(x2) fadd.d f10, f0, f1 ret
最初の8つの引数はレジスタ経由で渡されていることが分かる。8個以降は、レジスタが足りないのでスタック経由で引数を渡している。
./bin/clang -O3 fp_args.cpp -emit-llvm ./bin/llc -filetype=asm fp_args.bc -mcpu=simple32 -march=myriscvx32 -target-abi=lp64 -relocation-model=static -o -
- STACK32の場合。
test_dp_arg()
のコンパイル結果。float
の場合は以下のようなコードとなる。
_Z11test_dp_argfff: .cfi_startproc .frame x8,0,x1 .mask 0x00000000,0 .set noreorder .set nomacro # %bb.0: # %entry flw f0, 4(x2) flw f1, 0(x2) fadd.s f0, f1, f0 flw f1, 8(x2) fadd.s f10, f0, f1 ret
全ての引数をスタックに積んで渡していることが分かる。戻り値はf10
レジスタを経由して戻している。
- STACK32の場合。
test_dp_longarg()
のコンパイル結果。float
の場合は以下のようなコードとなる。
_Z15test_dp_longargdddddddddd: # %bb.0: # %entry fld f0, 8(x2) fld f1, 0(x2) fadd.d f0, f1, f0 fld f1, 16(x2) fadd.d f0, f0, f1 fld f1, 24(x2) fadd.d f0, f0, f1 fld f1, 32(x2) fadd.d f0, f0, f1 fld f1, 40(x2) fadd.d f0, f0, f1 fld f1, 48(x2) fadd.d f0, f0, f1 fld f1, 56(x2) fadd.d f0, f0, f1 fld f1, 64(x2) fadd.d f0, f0, f1 fld f1, 72(x2) fadd.d f10, f0, f1 ret
こちらもすべての引数をスタックに積んで渡していることが分かる。戻り値はf10
レジスタを経由して戻している。