FPGA開発日記

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

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

f:id:msyksphinz:20190425001356p:plain

RISC-Vには、浮動小数の比較命令命令として以下が定義されている。

funct7 rs2 rs1 funct3 rd opcode
FMIN.D 0010101 rs2 rs1 000 rd 1010011
FMAX.D 0010101 rs2 rs1 001 rd 1010011
FEQ.D 1010001 rs2 rs1 010 rd 1010011
FLT.D 1010001 rs2 rs1 001 rd 1010011
FLE.D 1010001 rs2 rs1 000 rd 1010011
FMIN.S 0010100 rs2 rs1 000 rd 1010011
FMAX.S 0010100 rs2 rs1 001 rd 1010011
FEQ.S 1010000 rs2 rs1 010 rd 1010011
FLT.S 1010000 rs2 rs1 001 rd 1010011
FLE.S 1010000 rs2 rs1 000 rd 1010011

これらの命令について、パタンを登録するだけだ。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfoFD.td
// Arithmetic and logical instructions with 2 register operands.
class FPCompDestR<bits<7> opcode, bits<3> funct3, bits<7>funct7,
                  string instr_asm, SDPatternOperator OpNode,
                  RegisterClass RC> :
  MYRISCVX_R<opcode, funct3, funct7, (outs GPR:$rd), (ins RC:$rs1, RC:$rs2),
  !strconcat(instr_asm, "\t$rd, $rs1, $rs2"),
  [(set GPR:$rd, (OpNode RC:$rs1, RC:$rs2))], IIAlu> {
    let isReMaterializable = 1;
}

def FMAX_S  : ArithLogicR<0b1010011, 0b001, 0b0010100, "fmax.s", fmaxnum, FPR_S>;
def FMIN_S  : ArithLogicR<0b1010011, 0b000, 0b0010100, "fmin.s", fminnum, FPR_S>;
def FEQ_S   : FPCompDestR<0b1010011, 0b010, 0b1010000, "feq.s" , seteq,   FPR_S>;
def FLT_S   : FPCompDestR<0b1010011, 0b001, 0b1010000, "flt.s" , setlt,   FPR_S>;
def FLE_S   : FPCompDestR<0b1010011, 0b000, 0b1010000, "fle.s" , setle,   FPR_S>;

def FMAX_D  : ArithLogicR<0b1010011, 0b001, 0b0010101, "fmax.s", fmaxnum, FPR_D>;
def FMIN_D  : ArithLogicR<0b1010011, 0b000, 0b0010101, "fmin.s", fminnum, FPR_D>;
def FEQ_D   : FPCompDestR<0b1010011, 0b010, 0b1010001, "feq.d" , seteq,   FPR_D>;
def FLT_D   : FPCompDestR<0b1010011, 0b001, 0b1010001, "flt.d" , setlt,   FPR_D>;
def FLE_D   : FPCompDestR<0b1010011, 0b000, 0b1010001, "fle.d" , setle,   FPR_D>;

上記の10命令を登録した。また、比較命令のためのパタンとしてFPCompDestRを定義した。 その名の通り、浮動小数レジスタ通しの値を比較して、その結果を汎用レジスタに格納するパタンだ。

FPCompDestRクラスでは、命令の定義と同時に命令生成のパタンも登録している。 この命令定義ではseteq, setlt, setleを命令のパタンとして登録しているが、LLVMにはもう一つ比較のパタンが存在する。 setoeq, setolt, setoleだ。これらは浮動小数点命令特有の演算で、Ordered Opsと言われている。これらのパタンでも命令を生成できるように、パタンを追加する。

// For Single-Floating Point
def : Pat<(setoeq FPR_S:$rs1, FPR_S:$rs2), (FEQ_S $rs1, $rs2)>;
def : Pat<(setolt FPR_S:$rs1, FPR_S:$rs2), (FLT_S $rs1, $rs2)>;
def : Pat<(setole FPR_S:$rs1, FPR_S:$rs2), (FLE_S $rs1, $rs2)>;
// For greater / greater eq pattern. Reverse operand
def : Pat<(setogt FPR_S:$rs1, FPR_S:$rs2), (FLE_S $rs2, $rs1)>;
def : Pat<(setoge FPR_S:$rs1, FPR_S:$rs2), (FLT_S $rs2, $rs1)>;

def : Pat<(setgt  FPR_S:$rs1, FPR_S:$rs2), (FLE_S $rs2, $rs1)>;
def : Pat<(setge  FPR_S:$rs1, FPR_S:$rs2), (FLT_S $rs2, $rs1)>;

// For Double-Floating Point
def : Pat<(setoeq FPR_D:$rs1, FPR_D:$rs2), (FEQ_D $rs1, $rs2)>;
def : Pat<(setolt FPR_D:$rs1, FPR_D:$rs2), (FLT_D $rs1, $rs2)>;
def : Pat<(setole FPR_D:$rs1, FPR_D:$rs2), (FLE_D $rs1, $rs2)>;
// For greater / greater eq pattern. Reverse operand
def : Pat<(setogt FPR_D:$rs1, FPR_D:$rs2), (FLE_D $rs2, $rs1)>;
def : Pat<(setoge FPR_D:$rs1, FPR_D:$rs2), (FLT_D $rs2, $rs1)>;
def : Pat<(setgt  FPR_D:$rs1, FPR_D:$rs2), (FLE_D $rs2, $rs1)>;
def : Pat<(setge  FPR_D:$rs1, FPR_D:$rs2), (FLT_D $rs2, $rs1)>;

さらに注意だ。setle, setlt, setole, setoltだけでなく、その逆もある。setgt, setge, setogt, setogeだ。 これらはle, ltオペランドを逆にすればよいので、これらのパタンも追加している。

また、命令のエイリアスとしてfge.s, fgt.s, fge.d, fgt.dを追加した。これはアセンブラに対応させるための追加だ。

def : InstAlias<"fge.s $rd, $rs1, $rs2",
                (FLT_S GPR:$rd, FPR_S:$rs2, FPR_S:$rs1), 0>;
def : InstAlias<"fgt.s $rd, $rs1, $rs2",
                (FLE_S GPR:$rd, FPR_S:$rs2, FPR_S:$rs1), 0>;

def : InstAlias<"fge.d $rd, $rs1, $rs2",
                (FLT_D GPR:$rd, FPR_D:$rs2, FPR_D:$rs1), 0>;
def : InstAlias<"fgt.d $rd, $rs1, $rs2",
                (FLE_D GPR:$rd, FPR_D:$rs2, FPR_D:$rs1), 0>;

ここまでで、LLVMをビルドしてテストを流してみる。以下のようなC言語のコードをテストする。

  • fp_cmp.cpp
int fp_lt_cmp(float a, float b) { return a <  b; }
int fp_le_cmp(float a, float b) { return a <= b; }
int fp_gt_cmp(float a, float b) { return a >  b; }
int fp_ge_cmp(float a, float b) { return a >= b; }

int dp_lt_cmp(double a, double b) { return a <  b; }
int dp_le_cmp(double a, double b) { return a <= b; }
int dp_gt_cmp(double a, double b) { return a >  b; }
int dp_ge_cmp(double a, double b) { return a >= b; }
./bin/clang -O3 fp_cmp.cpp -emit-llvm
./bin/llc -filetype=asm fp_cmp.bc -mcpu=simple32 -march=myriscvx32 -o -

結果は以下のようになった。正しく命令が生成できていることが分かる。

_Z9fp_lt_cmpff:
    flt.s  x10, f10, f11
    ret
_Z9fp_le_cmpff:
    fle.s  x10, f10, f11
    ret
_Z9fp_gt_cmpff:
    fle.s  x10, f11, f10
    ret
_Z9fp_ge_cmpff:
    flt.s  x10, f11, f10
    ret
_Z9dp_lt_cmpdd:
    flt.d  x10, f10, f11
    ret
_Z9dp_le_cmpdd:
    fle.d  x10, f10, f11
    ret
_Z9dp_gt_cmpdd:
    fle.d  x10, f11, f10
    ret
_Z9dp_ge_cmpdd:
    flt.d  x10, f11, f10
    ret