浮動小数点のロードストア命令が生成できるようになったので、次は簡単な算術演算ができるようにする。とりあえずは、加減乗除、そして積和演算ができるようになりたい。
私たちはすでに命令定義のクラスとテンプレートを持っているので、そこに当てはめるだけで良い。
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
が生成することができる。
さらに、積和演算も定義します。積和演算命令は以下のようなフォーマットになっている。
このフォーマットで、積和演算用の命令クラスを作成します。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; }
このテンプレートを元に、単精度用と倍精度用の積和演算命令を追加する。
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
無事に浮動小数点演算命令を生成することができた。 ここでは省略しているが、倍精度命令のテストも正しく命令を生成することができている。