FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (49. 浮動小数点算術演算命令の追加)

f:id:msyksphinz:20190425001356p:plain

浮動小数点のロードストア命令が生成できるようになったので、次は簡単な算術演算ができるようにする。とりあえずは、加減乗除、そして積和演算ができるようになりたい。

私たちはすでに命令定義のクラスとテンプレートを持っているので、そこに当てはめるだけで良い。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfoFD.td
def FADD_S   : ArithLogicR<0b1010011, 0b000, 0b0000000, "fadd.s", fadd, FPR_S>;
def FSUB_S   : ArithLogicR<0b1010011, 0b000, 0b0000100, "fsub.s", fsub, FPR_S>;
def FMUL_S   : ArithLogicR<0b1010011, 0b000, 0b0001000, "fmul.s", fmul, FPR_S>;
def FDIV_S   : ArithLogicR<0b1010011, 0b000, 0b0001100, "fdiv.s", fdiv, FPR_S>;

def FADD_D   : ArithLogicR<0b1010011, 0b000, 0b0000001, "fadd.d", fadd, FPR_D>;
def FSUB_D   : ArithLogicR<0b1010011, 0b000, 0b0000101, "fsub.d", fsub, FPR_D>;
def FMUL_D   : ArithLogicR<0b1010011, 0b000, 0b0001001, "fmul.d", fmul, FPR_D>;
def FDIV_D   : ArithLogicR<0b1010011, 0b000, 0b0001101, "fdiv.d", fdiv, FPR_D>;

単精度版では、使用するレジスタとしてFPR_S、倍精度版ではFPR_Dを指定した。 それぞれ、生成パタンに使用する演算としてはfadd, fsub, fmul, fdivを指定している。 これだけで、fadd.s, fsub.s, fmul.s, fdiv.s, fadd.d, fsub.d, fmul.d, fdiv.dが生成することができる。

さらに、積和演算も定義します。積和演算命令は以下のようなフォーマットになっている。

1564738842189

このフォーマットで、積和演算用の命令クラスを作成します。MYRISCVX_FMAクラスとする。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrFormats.td
//===----------------------------------------------------------------------===//
// FMA-Type instruction class in MYRISCVX : <|opcode|rd|rs1|rs2|rs3|>
//===----------------------------------------------------------------------===//
class MYRISCVX_FMA<bits<7> opcode, bits<2> fmt, bits<3> rm,
                   dag outs, dag ins, string asmstr, list<dag> pattern,
                   InstrItinClass itin>:
  MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmR>
{
  bits<5> rs3;
  bits<5> rs2;
  bits<5> rs1;
  bits<5> rd;

  let Inst{31-27} = rs3;
  let Inst{26-25} = fmt;
  let Inst{24-20} = rs2;
  let Inst{19-15} = rs1;
  let Inst{14-12} = rm;
  let Inst{11-7}  = rd;
  let Inst{6-0}   = opcode;
}

さらにMYRISCVX_FMAクラスを使いやすくした、FPMultAddクラスを作成しておく。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfoFD.td
// Floating-Point FMA instructions with 3 register operands.
class FPMultAdd<bits<7> opcode, bits<2> fmt, bits<3> rm,
                string instr_asm,
                RegisterClass RC> :
  MYRISCVX_FMA<opcode, fmt, rm, (outs RC:$rd), (ins RC:$rs1, RC:$rs2, RC:$rs3),
               !strconcat(instr_asm, "\t$rd, $rs1, $rs2, $rs3"),
               [], IIAlu> {
    let isReMaterializable = 1;
}

このテンプレートを元に、単精度用と倍精度用の積和演算命令を追加する。

f:id:msyksphinz:20190802231032p:plain
浮動小数点積和演算命令を定義するためのクラステンプレート
  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfoFD.td
//
// Single Floating Point Operations
//
def FMADD_S  : FPMultAdd<0b1010011, 0b00, 0b000, "fmadd.s",  FPR_S>;
def FMSUB_S  : FPMultAdd<0b1000111, 0b00, 0b000, "fmsub.s",  FPR_S>;
def FNMSUB_S : FPMultAdd<0b1001011, 0b00, 0b000, "fnmsub.s", FPR_S>;
def FNMADD_S : FPMultAdd<0b1001111, 0b00, 0b000, "fnmadd.s", FPR_S>;

//
// Double Floating Point Operations
//
def FMADD_D  : FPMultAdd<0b1010011, 0b01, 0b000, "fmadd.d",  FPR_D>;
def FMSUB_D  : FPMultAdd<0b1000111, 0b01, 0b000, "fmsub.d",  FPR_D>;
def FNMSUB_D : FPMultAdd<0b1001011, 0b01, 0b000, "fnmsub.d", FPR_D>;
def FNMADD_D : FPMultAdd<0b1001111, 0b01, 0b000, "fnmadd.d", FPR_D>;

さらに、これらの命令の生成パタンを追加する。FMADDに関してはfmaというパタンがあるのだが、それ以外についてはデフォルトで用意されていないので、手動で追加する。

// Floating-Point FMA pattern registration
def : Pat<(fma FPR_S:$rs1, FPR_S:$rs2, FPR_S:$rs3),               (FMADD_S  $rs1, $rs2, $rs3)                  >;
def : Pat<(fma FPR_S:$rs1, FPR_S:$rs2, (fneg FPR_S:$rs3)),        (FMSUB_S  FPR_S:$rs1, FPR_S:$rs2, FPR_S:$rs3)>;
def : Pat<(fma (fneg FPR_S:$rs1), FPR_S:$rs2, FPR_S:$rs3),        (FNMSUB_S FPR_S:$rs1, FPR_S:$rs2, FPR_S:$rs3)>;
def : Pat<(fma (fneg FPR_S:$rs1), FPR_S:$rs2, (fneg FPR_S:$rs3)), (FNMADD_S FPR_S:$rs1, FPR_S:$rs2, FPR_S:$rs3)>;

// Floating-Point FMA pattern registration
def : Pat<(fma FPR_D:$rs1, FPR_D:$rs2, FPR_D:$rs3),               (FMADD_D  $rs1, $rs2, $rs3)                  >;
def : Pat<(fma FPR_D:$rs1, FPR_D:$rs2, (fneg FPR_D:$rs3)),        (FMSUB_D  FPR_D:$rs1, FPR_D:$rs2, FPR_D:$rs3)>;
def : Pat<(fma (fneg FPR_D:$rs1), FPR_D:$rs2, FPR_D:$rs3),        (FNMSUB_D FPR_D:$rs1, FPR_D:$rs2, FPR_D:$rs3)>;
def : Pat<(fma (fneg FPR_D:$rs1), FPR_D:$rs2, (fneg FPR_D:$rs3)), (FNMADD_D FPR_D:$rs1, FPR_D:$rs2, FPR_D:$rs3)>;

ここまで出来たら、LLVMをビルドしてテストコードを流してみる。以下のようなテストコードを用意した。

  • fp_ops.cpp
#include <math.h>

void test_fp_math(float *a, float *b, float *c)
{
  float res_add = *a + *b + *c;
  float res_sub = *a - *b - *c;
  float res_mul = *a * *b * *c;
  float res_div = *a / *b / *c;

  float res_fmad  = fmaf(*a, *b, *c);
  // float res_fsub  = *a * *b - *c;
  // float res_fnmsub = (-*a) * *b + *c;
  // float res_fnmadd = (-*a) * *b - *c;
}

void test_dp_math(double *a, double *b, double *c)
{
  double res_add = *a + *b + *c;
  double res_sub = *a - *b - *c;
  double res_mul = *a * *b * *c;
  double res_div = *a / *b / *c;

  double res_fmad  = fma(*a, *b, *c);
  // double res_fsub  = *a * *b - *c;
  // double res_fnmsub = (-*a) * *b + *c;
  // double res_fnmadd = (-*a) * *b - *c;
}
./bin/clang -O0 fp_ops.cpp -emit-llvm
./bin/llc -filetype=asm fp_ops.bc -mcpu=simple32 -march=myriscvx32 -target-abi=lp32 -o -

このコードは、値は引数から取得しているが、値そのものはポインタを使用している。 何故なら、浮動小数点のCalling Conventionをここではまだ定義していないので、浮動小数レジスタ経由での値の受け渡しができないからだ。 これは、後で追加する。また、それぞれの計算結果自体はその場で捨てており、関数としてみると無駄なコードなので、最適化オプションを付けると消されてしまっている。 -O0で実行する。

生成されたアセンブリ命令を見てみる。

_Z12test_fp_mathPfS_S_:
# %bb.0:                                # %entry
        addi    x2, x2, -32
        .cfi_def_cfa_offset 32
        lw      x10, 40(x2)
        lw      x10, 36(x2)
        lw      x11, 32(x2)
        sw      x10, 24(x2)
        lw      x10, 32(x2)
        flw     f0, 0(x10)
        lw      x10, 24(x2)
        flw     f1, 0(x10)
        fadd.s  f0, f0, f1
        lw      x10, 40(x2)
        flw     f1, 0(x10)
        fadd.s  f0, f0, f1
        fsw     f0, 20(x2)
...
        fsub.s  f0, f0, f1
...
        fsub.s  f0, f0, f1
...
        fmul.s  f0, f0, f1
...
        fmul.s  f0, f0, f1
...
        fdiv.s  f0, f0, f1
...
        fdiv.s  f0, f0, f1
...
        fmadd.s f0, f0, f1, f2
...
        fmadd.s f0, f0, f1, f2

無事に浮動小数点演算命令を生成することができた。 ここでは省略しているが、倍精度命令のテストも正しく命令を生成することができている。