FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

LLVMのバックエンドを作るための第一歩 (50. 浮動小数点レジスタ向けのCalling Conventionの追加)

f:id:msyksphinz:20190425001356p:plain

浮動小数点演算命令を使った算術演算は生成できるようになったが、まだ関数の引数として浮動小数点型を使用することができない。 これは、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を用意する。

  • lp32 : 最初の8個までの引数は浮動小数レジスタf10 - f17で渡す。それから先の引数はスタック経由で渡す。
  • stack32 : 全ての浮動小数点引数をスタック経由で渡す。

このため、MYRISCVXCallingConv.tdに記述の追加を行う。

f:id:msyksphinz:20190802231859p:plain
浮動小数点型に対するCalling Conventionの定義
  • 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レジスタを経由して戻している。