FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (52. 浮動小数点のその他の命令)

f:id:msyksphinz:20190425001356p:plain

比較命令以外にも、浮動小数点の様々な演算をサポートする。例えば、以下のようなものが挙げられる。

これらの操作について、RISC-Vの命令を定義してサポートを行う。RISC-Vには、浮動小数点のこれらのサポート命令として以下が定義されている。

funct7 rs2 rs1 funct3 rd opcode
FSQRT.S 0101100 00000 rs1 rm rd 1010011
FSGNJ.S 0010000 rs2 rs1 000 rd 1010011
FSIGNJN.S 0010000 rs2 rs1 001 rd 1010011
FSGNJX.S 0010000 rs2 rs1 010 rd 1010011
FCVT.W.S 1100000 00000 rs1 000 rd 1010011
FCVT.WU.S 1100000 00001 rs1 001 rd 1010011
FMV.X.W 1110000 00000 rs1 000 rd 1010011
FCLASS.S 1110000 00000 rs1 001 rd 1010011
FCVT.S.W 1101000 00000 rs1 rm rd 1010011
FCVT.S.WU 1101000 00001 rs1 rm rd 1010011
FMV.W.X 1111000 00000 rs1 000 rd 1010011
funct7 rs2 rs1 funct3 rd opcode
FSQRT.D 0101101 00000 rs1 rm rd 1010011
FSGNJ.D 0010001 rs2 rs1 000 rd 1010011
FSIGNJN.D 0010001 rs2 rs1 001 rd 1010011
FSGNJX.D 0010001 rs2 rs1 010 rd 1010011
FCVT.W.D 1100001 00000 rs1 000 rd 1010011
FCVT.WU.D 1100001 00001 rs1 001 rd 1010011
FCLASS.D 1110001 00000 rs1 001 rd 1010011
FCVT.D.W 1101001 00000 rs1 rm rd 1010011
FCVT.D.WU 1101001 00001 rs1 rm rd 1010011

これらの命令を実装していくことにする。MYRISCVXInstrInfoFD.tdに以下を追加する。

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


// Floating-Point instructions with 1 register operands.
class FPSingleOp<bits<7> opcode, bits<3> rm, bits<7> funct7, bits<5> rs2_op,
                string instr_asm,
                RegisterClass DstRC, RegisterClass SrcRC> :
  MYRISCVX_R<opcode, rm, funct7, (outs DstRC:$rd), (ins SrcRC:$rs1),
             !strconcat(instr_asm, "\t$rd, $rs1"),
             [], IIAlu> {
    let isReMaterializable = 1;
    let rs2 = rs2_op;
  }

まずは、1オペランド用の命令と2オペランド用の命令テンプレートを用意した。それぞれFPSingleOp, FPTwoOpというテンプレートクラスを作成する。少し注意だが、FPSingleOpテンプレートでは、型の返還命令が入るため入力オペランドレジスタタイプ(RegisterClass SrcRC)と出力オペランドレジスタタイプ(RegisterClass DstRC)が個別に指定できる。これに基づいて、まずは単精度浮動小数点命令の定義を行う。

def FSQRT_S   : FPSingleOp<0b1010011, 0b000, 0b0101100, 0b00000, "fsqrt.s",  FPR_S, FPR_S>;
def FSIGNJ_S  : FPTwoOp<0b1010011, 0b000, 0b0010000, "fsgnj.s" , FPR_S>;
def FSIGNJN_S : FPTwoOp<0b1010011, 0b001, 0b0010000, "fsgnjn.s", FPR_S>;
def FSIGNJX_S : FPTwoOp<0b1010011, 0b010, 0b0010000, "fsgnjx.s", FPR_S>;
def FCVT_W_S  : FPSingleOp<0b1010011, 0b000, 0b1100000, 0b00000, "fcvt.w.s",  GPR, FPR_S>;
def FCVT_WU_S : FPSingleOp<0b1010011, 0b000, 0b1100000, 0b00001, "fcvt.wu.s", GPR, FPR_S>;
def FMV_X_W   : FPSingleOp<0b1010011, 0b000, 0b1110000, 0b00000, "fmv.x.w",   GPR, FPR_S>;
def FCLASS_S  : FPSingleOp<0b1010011, 0b001, 0b1110000, 0b00000, "fclass.s",  FPR_S, FPR_S>;
def FCVT_S_W  : FPSingleOp<0b1010011, 0b000, 0b1101000, 0b00000, "fcvt.s.w",  FPR_S, GPR>;
def FCVT_S_WU : FPSingleOp<0b1010011, 0b000, 0b1101000, 0b00001, "fcvt.s.wu", FPR_S, GPR>;
def FMV_W_X   : FPSingleOp<0b1010011, 0b000, 0b1111000, 0b00000, "fmv.w.x",   FPR_S, GPR>;

次に、これらの命令を生成するためのSelectionDAGのパタンを追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfoFD.td
def : Pat<(fsqrt FPR_S:$rs1), (FSQRT_S   $rs1)>;
def : Pat<(fneg  FPR_S:$rs1), (FSIGNJN_S $rs1, $rs1)>;
def : Pat<(fabs  FPR_S:$rs1), (FSIGNJX_S $rs1, $rs1)>;
def : InstAlias<"fmv.s  $rd, $rs1", (FSIGNJ_S  FPR_S:$rd, FPR_S:$rs1, FPR_S:$rs1), 0>;
def : InstAlias<"fneg.s $rd, $rs1", (FSIGNJN_S FPR_S:$rd, FPR_S:$rs1, FPR_S:$rs1), 0>;
def : InstAlias<"fabs.s $rd, $rs1", (FSIGNJX_S FPR_S:$rd, FPR_S:$rs1, FPR_S:$rs1), 0>;
def : Pat<(fp_to_sint FPR_S:$rs1), (FCVT_W_S  $rs1)>;
def : Pat<(fp_to_uint FPR_S:$rs1), (FCVT_WU_S $rs1)>;
def : Pat<(sint_to_fp GPR:$rs1), (FCVT_S_W  $rs1)>;
def : Pat<(uint_to_fp GPR:$rs1), (FCVT_S_WU $rs1)>;

エイリアスとしてfmv.s, fneg.s, fabs.sを追加した。アセンブリでは、これらの記法も有効になる。

同様に、倍精度浮動小数点についても命令の定義とパタンを追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfoFD.td
def FSQRT_D   : FPSingleOp<0b1010011, 0b000, 0b0101101, 0b00000, "fsqrt.d",  FPR_D, FPR_D>;
def FSIGNJ_D  : FPTwoOp<0b1010011, 0b000, 0b0010001, "fsgnj.d" , FPR_D>;
def FSIGNJN_D : FPTwoOp<0b1010011, 0b001, 0b0010001, "fsgnjn.d", FPR_D>;
def FSIGNJX_D : FPTwoOp<0b1010011, 0b010, 0b0010001, "fsgnjx.d", FPR_D>;
def FCVT_W_D  : FPSingleOp<0b1010011, 0b000, 0b1100001, 0b00000, "fcvt.w.d",  GPR, FPR_D>;
def FCVT_WU_D : FPSingleOp<0b1010011, 0b000, 0b1100001, 0b00001, "fcvt.wu.d", GPR, FPR_D>;
// def FMV_X_W   : FPSingleOp<0b1010011, 0b000, 0b1110001, 0b00000, "fmv.x.w",   GPR, FPR_D>;
def FCLASS_D  : FPSingleOp<0b1010011, 0b001, 0b1110001, 0b00000, "fclass.d",  FPR_D, FPR_D>;
def FCVT_D_W  : FPSingleOp<0b1010011, 0b000, 0b1101001, 0b00000, "fcvt.d.w",  FPR_D, GPR>;
def FCVT_D_WU : FPSingleOp<0b1010011, 0b000, 0b1101001, 0b00001, "fcvt.d.wu", FPR_D, GPR>;
// def FMV_W_X   : FPSingleOp<0b1010011, 0b000, 0b1111001, 0b00000, "fmv.w.x",   FPR_D, GPR>;

def : Pat<(fsqrt FPR_D:$rs1), (FSQRT_D $rs1)>;
def : Pat<(fneg  FPR_D:$rs1), (FSIGNJN_D $rs1, $rs1)>;
def : Pat<(fabs  FPR_D:$rs1), (FSIGNJX_D $rs1, $rs1)>;
def : InstAlias<"fmv.d  $rd, $rs1", (FSIGNJ_D  FPR_D:$rd, FPR_D:$rs1, FPR_D:$rs1), 0>;
def : InstAlias<"fneg.d $rd, $rs1", (FSIGNJN_D FPR_D:$rd, FPR_D:$rs1, FPR_D:$rs1), 0>;
def : InstAlias<"fabs.d $rd, $rs1", (FSIGNJX_D FPR_D:$rd, FPR_D:$rs1, FPR_D:$rs1), 0>;
def : Pat<(fp_to_sint FPR_D:$rs1), (FCVT_W_D  $rs1)>;
def : Pat<(fp_to_uint FPR_D:$rs1), (FCVT_WU_D $rs1)>;
def : Pat<(sint_to_fp GPR:$rs1), (FCVT_D_W  $rs1)>;
def : Pat<(uint_to_fp GPR:$rs1), (FCVT_D_WU $rs1)>;

それでは、テストパタンを作成してコンパイルしてみる。

  • fp_others.cpp
#include <math.h>
#include <stdint.h>

// float f_sqrt (float in) { return sqrtf(in); }
float f_abs  (float in) { return fabsf(in); }
float f_neg  (float in) { return -in; }

// double d_sqrt (double in) { return sqrt(in); }
double d_abs  (double in) { return fabs(in); }
double d_neg  (double in) { return -in; }

int32_t  cvt_fp_to_sint (float  in) { return static_cast< int32_t>(in); }
uint32_t cvt_fp_to_uint (float  in) { return static_cast<uint32_t>(in); }
int32_t  cvt_dp_to_sint (double in) { return static_cast< int32_t>(in); }
uint32_t cvt_dp_to_uint (double in) { return static_cast<uint32_t>(in); }

float  cvt_sint_to_fp (int32_t  in) { return static_cast<float >(in); }
float  cvt_uint_to_fp (uint32_t in) { return static_cast<float >(in); }
double cvt_sint_to_dp (int32_t  in) { return static_cast<double>(in); }
double cvt_uint_to_dp (uint32_t in) { return static_cast<double>(in); }

単純な関数群を並べた。型の変換や、1オペランドの演算を並べる。これらがどのようにコンパイルされるのかをテストする。

./bin/clang -O3 fp_others.cpp -emit-llvm
./bin/llc -filetype=asm fp_others.bc -mcpu=simple32 -march=myriscvx32 -target-abi=lp64 -o -
_Z5f_absf:
        fsgnjx.s        f10, f10, f10
        ret
_Z5f_negf:
        fsgnjn.s        f10, f10, f10
        ret
_Z5d_absd:
        fsgnjx.d        f10, f10, f10
        ret
_Z5d_negd:
        fsgnjn.d        f10, f10, f10
        ret
_Z14cvt_fp_to_sintf:
        fcvt.w.s        x10, f10
        ret
_Z14cvt_fp_to_uintf:
        fcvt.wu.s       x10, f10
        ret
_Z14cvt_dp_to_sintd:
        fcvt.w.d        x10, f10
        ret
_Z14cvt_dp_to_uintd:
        fcvt.wu.d       x10, f10
        ret
_Z14cvt_sint_to_fpi:
        fcvt.s.w        f10, x10
        ret
_Z14cvt_uint_to_fpj:
        fcvt.s.wu       f10, x10
        ret
_Z14cvt_sint_to_dpi:
        fcvt.d.w        f10, x10
        ret
_Z14cvt_uint_to_dpj:
        fcvt.d.wu       f10, x10
        ret

正しく命令が生成できていそうだ。

LLVMのオリジナルバックエンド実装をRelease 9.0のブランチに移植してみる

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20190425/20190425001356.png

LLVM Release 9.0のリリースが目前だ。release_90のブランチでは日進月歩でコミットが進んでいるようだ。

github.com

これまではrelease_80ブランチの上で作業していたのだが、そろそろrelease_90の上に移ってちゃんと移植できるか確かめた方が良い気がしてきた。 Release_90とRelease_80でどのような違いがあるのか、見ておきたい。

このためには、git rebase --ontoでこれまでのコミットのベースとなるブランチをrelease_80からrelease_90に移動する。

git rebase --onto release_90 release_80 myriscvx/impl80
              G - H - I (myriscvx/impl80ブランチ)
             /
            D - E - F (release_90ブランチ)
           /
- A - B - C (release_80ブランチ)
                       G - H - I (myriscvx/impl90ブランチ)
                      /
             D - E - F (release_90ブランチ)
           /
- A - B - C (release_80ブランチ)

一応これでブランチの移動ができた。これでビルドを行ってエラーとなった点を見つけだしていく。

  • レジスタRegisterが導入された。getFrameRegisterなどの戻り値を変えた。
diff --git a/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp b/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp
index 6e63a194e1e..d62c757e745 100644
--- a/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp
+++ b/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp
@@ -175,8 +175,8 @@ void MYRISCVXAsmPrinter::printHex32(unsigned Value, raw_ostream &O) {
 void MYRISCVXAsmPrinter::emitFrameDirective() {
   const TargetRegisterInfo &RI = *MF->getSubtarget().getRegisterInfo();

-  unsigned stackReg  = RI.getFrameRegister(*MF);
-  unsigned returnReg = RI.getRARegister();
+  Register stackReg  = RI.getFrameRegister(*MF);
+  Register returnReg = RI.getRARegister();
   unsigned stackSize = MF->getFrameInfo().getStackSize();

   if (OutStreamer->hasRawTextSupport())
diff --git a/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.cpp b/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.cpp
index 7d462ba03cd..21fd93bb2e7 100644
--- a/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.cpp
+++ b/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.cpp
@@ -162,7 +162,7 @@ MYRISCVXRegisterInfo::trackLivenessAfterRegAlloc(const MachineFunction &MF) cons
 }

 // pure virtual method
-unsigned MYRISCVXRegisterInfo::
+Register MYRISCVXRegisterInfo::
 getFrameRegister(const MachineFunction &MF) const {
   return MYRISCVX::FP;
 }
diff --git a/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.h b/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.h
index 8604ff26cf6..95743e9161f 100644
--- a/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.h
+++ b/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.h
@@ -53,7 +53,7 @@ namespace llvm {
                              RegScavenger *RS = nullptr) const override;

     /// Debug information queries.
-    unsigned getFrameRegister(const MachineFunction &MF) const override;
+    Register getFrameRegister(const MachineFunction &MF) const override;

     /// \brief Return GPR register class.
     virtual const TargetRegisterClass *intRegClass(unsigned Size) const = 0;
  • AsmPrinter()APIの一部引数が変更されている。
@@ -285,8 +285,7 @@ void MYRISCVXAsmPrinter::PrintDebugValueComment(const MachineInstr *MI,

 // Print out an operand for an inline asm expression.
 bool MYRISCVXAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNum,
-                                         unsigned AsmVariant,const char *ExtraCode,
-                                         raw_ostream &O) {
+                                         const char *ExtraCode, raw_ostream &O) {
   // Does this asm operand have a single letter operand modifier?
   if (ExtraCode && ExtraCode[0]) {
     if (ExtraCode[1] != 0) return true; // Unknown modifier.
@@ -295,7 +294,7 @@ bool MYRISCVXAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNum,
     switch (ExtraCode[0]) {
       default:
         // See if this is a generic print operand
-        return AsmPrinter::PrintAsmOperand(MI,OpNum,AsmVariant,ExtraCode,O);
+        return AsmPrinter::PrintAsmOperand(MI, OpNum, ExtraCode, O);
       case 'X': // hex const int
         if ((MO.getType()) != MachineOperand::MO_Immediate)
           return true;
@@ -335,10 +334,8 @@ bool MYRISCVXAsmPrinter::PrintAsmOperand(const MachineInstr *MI, unsigned OpNum,
 }


-bool MYRISCVXAsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI,
-                                               unsigned OpNum, unsigned AsmVariant,
-                                               const char *ExtraCode,
-                                               raw_ostream &O) {
+bool MYRISCVXAsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum,
+                                               const char *ExtraCode, raw_ostream &O) {
   int Offset = 0;
   // Currently we are expecting either no ExtraCode or 'D'
   if (ExtraCode) {
diff --git a/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.h b/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.h
index c8e0d31cc9d..9541e802ce6 100644
--- a/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.h
+++ b/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.h
@@ -74,11 +74,9 @@ namespace llvm {
                                      const MachineInstr *MI);

     bool PrintAsmOperand(const MachineInstr *MI, unsigned OpNo,
-                         unsigned AsmVariant, const char *ExtraCode,
-                         raw_ostream &O) override;
+                         const char *ExtraCode, raw_ostream &O) override;
     bool PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNum,
-                               unsigned AsmVariant, const char *ExtraCode,
-                               raw_ostream &O) override;
+                               const char *ExtraCode, raw_ostream &O) override;
     void printOperand(const MachineInstr *MI, int opNum, raw_ostream &O);
   };
 }

これだけの変更でビルドが完了した。テストも正しく動作したようだ。

高性能プロセッサの分岐予測のサーベイ論文を読んで分岐予測について学ぶ (9. ニューラル分岐予測器2)

プロセッサアーキテクチャについて再度復習その8。今回からニューラル分岐予測器。ニューラルネットを用いた分岐予測器の続き。

読んでいるのはA Survey of Techniques for Dynamic Branch Predictionという論文で、新規技術の解説ではないのだが、現在の有名どころの分岐予測技術についてまんべんなく解説してくれている論文だ。

  • A Survey of Techniques for Dynamic Branch Prediction
    • Sparsh Mittal

https://arxiv.org/pdf/1804.00261.pdf


7. ニューラル分岐予測器と実装テクニック(続き)

Jimenez[51]は、パーセプトロンと経路ベースの分岐予測器の一般化である区分線形分岐予測器を示している[48, 49]。すべての分岐Bについて、それらの分岐予測器はBに至るすべてのパスのコンポーネントを追跡する。それはBの結果と一致するために履歴内の特定の位置での分岐の可能性を追跡する。予測は、現在のパスのすべてのコンポーネントの相関を集約することによって行われる。全体として、それらの分岐予測器は、予測された非分岐を予測された分岐から分離するために現在の分岐への各経路に対して1つずつ、複数の線形関数を生成する。したがって、それらの分岐予測器は、図32(c)に示すように、線形的に分離不可能な分岐に対して有効だ。ストレージの予算や待ち時間に制約がないと仮定すると、それらの分岐予測器は他の分岐予測器よりも高い予測精度を達成する。彼らはさらに先読みパイプラインと小さな履歴長の値を使用する彼らの分岐予測器の実用的な実装を提案する。また、パス履歴における分岐先アドレスおよび分岐先アドレスは、それぞれモジュロN, Mとして記憶される。パーセプトロンベースの分岐予測器 [48]は分岐(M = 1)に単一の線形関数を利用し、パスベースのニューラル分岐予測器 [49]はすべての分岐(N = 1)を予測するために単一のグローバル区分線形関数を利用する。区分線形分岐予測器の特殊な場合がある[51]。彼らは彼らの分岐予測器の実用的なバージョンも他の予測器よりも優れていることを示している。

Jimenezら[28]の提案。単層パーセプトロンベースの分岐予測器とは異なり、バックプロパゲーションベースの分岐予測器を備えた多層パーセプトロンは線形分離不可能な関数を学習できるため、パーセプトロン分岐予測器よりも高い「漸近的」精度を提供することが期待される。しかし、実際には、逆伝播ニューラル分岐予測器はトレーニングと予測の両方に非常に長い時間がかかるため、精度とパフォーマンスが低くなり、逆伝播ベースの分岐予測器は実行不可能で無効になる[53]。

Tarjanらの提案[23]。gshare, パスベース、パーセプトロン分岐予測器のアイデアを使用した、ハッシュ化パーセプトロン分岐予測器を提案する。この手法では、パーセプトロン分岐予測器は、分岐の相関を測定するためにそれぞれの重みを使用しており、これは使用する履歴の量がが増えるとテーブルと加算器の数が線形に増加し、またそれぞれのカウンタは十分に大きく取り、重みがいくつかの他の重みをオーバライドすることができるようにする。グローバル分岐履歴のセグメントをPCとXORすることによって、複数の分岐に同じ重みを割り当てる分岐予測器を提案している。グローバル分岐/パス履歴との単一の一致を行う代わりに、それらは複数の部分一致を実行するためにそれを複数のセグメントに分割する。さらに、パス履歴を使用する代わりに、分岐PCのみを使用する。ハッシュインデックスでは、すべてのテーブルが小さなgshare 分岐予測器として機能するため、それらの分岐予測器は同じ重みにマッピングされた線形的に分離不可能な分岐も予測できる。また、パスベースの分岐予測器と比較して、分岐予測器は同じ履歴の長さに使用する重み数を少なくする。これにより、相関のない複数の重みが強い相関を持つ1つの重みを上書きする可能性が低くなる。グローバルまたはパスベースの分岐予測器と比較して、それらの分岐予測器はチェックポイントが取られる状態の量を減らす短いパイプラインを持っている。 分岐予測器の待ち時間を減らすために、彼らは先送りパイプラインを使用する。パスベースおよびグローバルニューラル分岐予測器と比較して、加算器の数を減らしながら、分岐予測器は精度を大幅に向上させる。

Gaoらの提案[22]。パーセプトロンベースの分岐予測器では、パーセプトロン重みの値が相関強度を示すことに注意する。たとえば、図34は4つの分岐を示している。分岐1は2つのランダム変数によって決定され、分岐2は分岐1と同じ確率変数(k1)を使用しているため相関する。分岐3は分岐1の逆であり、分岐4はXORで分岐2と分岐3と関連している。パーセプトロン分岐予測器は、各分岐に8ビットGHRのパーセプトロンが1つ使用されている場合に使用される。 100M命令シミュレーションのパーセプトロン重み(w1-w8)と予測ミス率は、図34の右側に表示されている。明らかに、Branch1は前の分岐と相関がないので、その重みはわずかなランダム性を持つ。Branch2は、Branch1との相関および小さいランダムw2-w8値のために、大きなw1(GHR [0]との強い相関)を持つ。Branch3の結果はBranch1の結果によって正確に判定できるので、Branch3は単一の大きな重みw2(GHR [1]との強い相関)を持つ。Branch4は、前の分岐と非線形に相関するため、重みが大きくなる。

f:id:msyksphinz:20190805005129p:plain
本論文より抜粋

さらに、分岐予測器の入力を再組み立てすることによって、その精度を改善することができ、例えば、最近の2つの分岐のみがNのうち最も相関が高い場合、パーセプトロンサイズをNから2に減らすことができる。また、弱い相関をより強い相関に置き換えることができ、例えば冗長履歴を使用することができ、これは線形的に分離不可能な分岐を処理するのに役立つ。彼らは、各ワークロードタイプに適した入力ベクトルを見つけるために静的プロファイリングを使用する。これはアプリケーションタイプに基づいて実行時に選択される。さらに、入力および位相挙動の変化を説明するために、相関強度は実行時に定期的にテストされ、弱い相関の入力は強い相関の入力によって置き換えられる。彼らの技術は分岐予測器の正確さを著しく改善する。

Akkary[24]らの提案。パーセプトロンベースの分岐信頼度推定器(Confidence Estimator:CE)を提案する。パーセプトロンへの入力は、T/NT分岐がそれぞれ1 / -1として記録されるグローバル分岐履歴だ。信頼推定量の訓練は、リタイア時に(すなわち、非投機的に)行われる。 p(=1は正しく予測された分岐、-1は誤って予測された分岐)とc(1は高い信頼度が割り当てられた分岐、-1は低い信頼度が割り当てられた分岐)とが異なる符号を有する場合、重みは$$w[i] += p ∗ x[i]$$として更新される。正しい/誤った予測を使用して彼らの分岐予測器を訓練することは、分岐/非分岐の結果を使用するより高い精度を達成する[48]。また、彼らのCEは、予測ミスされた分岐の妥当な範囲を提供する。さらに、彼らは、CEの非バイナリ出力を2つの領域に分ける。強い自信と弱い自信がなく、それから分岐反転とパイプラインゲーティングをそれぞれ適用する。これにより、単一のハードウェアを使用して分岐予測器およびパイプラインゲーティングを向上させることができる。図35に示すパイプラインゲーティングアプローチでは、複数の信頼度の低い分岐がフェッチされると命令のフェッチが停止する。これにより、誤った命令がパイプラインに入るのを防ぎ、エネルギーを節約する。

f:id:msyksphinz:20190805005226p:plain
本論文より抜粋

System Verilogで記述されたRISC-VコアArianeを試す (2. Arianeのキャッシュサブシステム)

Arianeは、System Verilogで記述されたインオーダの6ステージRISC-Vパイプラインプロセッサだ。

Arianeのデザインは良くできていて、System Verilogできれいに記述されている。キャッシュサブシステムなどCPUの基本構成も綺麗に設計されているので、これを機に勉強してみることにした。

Arianeのデータキャッシュは以下のような構成になっている。

github.com

https://github.com/msyksphinz/ariane/tree/master/src/cache_subsystemを見てみる。

wt_dcacheの構成は以下のようになっていた。

f:id:msyksphinz:20190814221452p:plain
ArianeのDCache Subsystemの構成

構成としてはポートの数だけwt_dcache_ctrlが用意されていて、書き込み用のwt_dcache_wbufferが用意されている。そしてミスが発生した場合の処理を行うwt_dcache_missunitが接続されており、キャッシュの本体であるwt_dcache_memに接続されている。

wt_dcache_ctrlはRead Port向けの制御コントローラだ。基本的にステートマシンで動いている。このステートマシンも若干あいまいだけれども、REPLAYというのが良く分からない。MISSが起きたときに、何度もリクエストをリトライしている、ということだろうか?

f:id:msyksphinz:20190814224420p:plain
wt_dcache_ctrlのステートマシン。曖昧なところあり。

wt_dcache_missunitのステートマシン。こちらはMSHRのステートを制御しているものと思われる。

f:id:msyksphinz:20190814223912p:plain
wt_dcache_missunitのステートマシン。曖昧なところあり。

missunitには、すべてのポートから一気にMissの通知が来る。これを裁くのがArbiterだ。Arbiterはlzcとして実装されており、最も若いポートが優先的に使用される。

  assign miss_req_masked_d = (lock_reqs)  ? miss_req_masked_q      :
                             (mask_reads) ? miss_we_i & miss_req_i : miss_req_i;
  assign miss_is_write     = miss_we_i[miss_port_idx];

  // read port arbiter
  lzc #(
    .WIDTH ( NumPorts )
  ) i_lzc_reqs (
    .in_i    ( miss_req_masked_d ),
    .cnt_o   ( miss_port_idx     ),
    .empty_o (                   )
  );

メモリロードリクエストにおいてミスが発生した場合には、ステートマシンがLOAD_WAITに移行し、MSHRに値が割り当てられる。 同じmshrレジスタにすでに値が入っている場合はCollisionとして再度Replayの指示が出る。

    unique case (state_q)
      //////////////////////////////////
      // wait for misses / amo ops
      IDLE: begin
...
        // we've got a miss to handle
        end else if (|miss_req_masked_d) begin
          // this is a write miss, just pass through (but check whether write collides with MSHR)
...
          // this is a read miss, can only allocate 1 MSHR
          // in case of a load_ack we can accept a new miss, since the MSHR is being cleared
          end else if (!mshr_vld_q || load_ack) begin
            // replay the read request in case the address has collided with MSHR during the time the request was pending
            // i.e., the cache state may have been updated in the mean time due to a refill at the same CL address
            if (mshr_rdrd_collision_d[miss_port_idx]) begin
              miss_replay_o[miss_port_idx] = 1'b1;
            end else if (!tx_rdwr_collision) begin
              mem_data_req_o            = 1'b1;
              mem_data_o.rtype          = DCACHE_LOAD_REQ;
              update_lfsr               = all_ways_valid & mem_data_ack_i;// need to evict a random way
              mshr_allocate             = mem_data_ack_i;
              if (!mem_data_ack_i) begin
                state_d = LOAD_WAIT;
              end

そしてMSHRに必要な情報のバックアップが取られる、という仕組みだ。

  assign mshr_d.size            = (mshr_allocate)  ? miss_size_i    [miss_port_idx] : mshr_q.size;
  assign mshr_d.paddr           = (mshr_allocate)  ? miss_paddr_i   [miss_port_idx] : mshr_q.paddr;
  assign mshr_d.vld_bits        = (mshr_allocate)  ? miss_vld_bits_i[miss_port_idx] : mshr_q.vld_bits;
  assign mshr_d.id              = (mshr_allocate)  ? miss_id_i      [miss_port_idx] : mshr_q.id;
  assign mshr_d.nc              = (mshr_allocate)  ? miss_nc_i      [miss_port_idx] : mshr_q.nc;
  assign mshr_d.repl_way        = (mshr_allocate)  ? repl_way                       : mshr_q.repl_way;
  assign mshr_d.miss_port_idx   = (mshr_allocate)  ? miss_port_idx                  : mshr_q.miss_port_idx;
...
always_ff @(posedge clk_i or negedge rst_ni) begin : p_regs
  if (!rst_ni) begin
...
    mshr_q                <= '0;
  end else begin
...
    mshr_q                <= mshr_d;
...

RustのコードをLLVM IRに変換してRISC-V LLVMバックエンドに渡してみる

f:id:msyksphinz:20190224185310p:plain

RustのフロントエンドはLLVMで実装されているらしい。

Rustの文法自体は最近触っていないせいですっかり忘れてしまったが、自作LLVMバックエンドがRustの生成したLLVMを処理することができるようになれば格好いいかもしれない。 とりあえず、RustからLLVMのIRを生成してLLVMバックエンドに渡してみる方法について調査した。

RustのコードからLLVM IRを生成するためには以下のようにすればよいらしい。(Rustでmain無しのライブラリのみ作るためにはどうしたらよいのだ...)

  • main.rs
fn main() {
    let _a:u32 = 1 + 2;
}

なんか最適化で全部消えてしまいそうな気がするのだが、仕方がない。私のRustの知識は現在は退化しており、これが限界だ。

さて、これからLLVM IRおよびLLVM Bitcodeを生成するためには以下のように処理すればよいらしい。

rustc main.rs --emit=llvm-ir  # main.llを生成する
rustc main.rs --emit=llvm-bc # main.bcを生成する

これを実行してみると、確かにLLVM IRが生成されている。しかし猛烈に長いな...

  • main.ll
; ModuleID = 'main.7rcbfp3g-cgu.0'
source_filename = "main.7rcbfp3g-cgu.0"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
...
; main::main
; Function Attrs: nonlazybind uwtable
define internal void @_ZN4main4main17hfe98083a4c87500fE() unnamed_addr #0 {
start:
  ret void
}

とりあえず、本家のRISC-V LLVMバックエンドに渡してみる。

./bin/llc -debug -march=riscv32 /home/msyksphinz/work/llvm/myriscvx-tests/rust/rust_simple/src/main.bc -o main.s

を、生成できた。中身を見てみると、RISC-Vのアセンブリが生成されている。良かった。

  • main.s
        .size   _ZN4main4main17hfe98083a4c87500fE, .Lfunc_end9-_ZN4main4main17hfe98083a4c87500fE
                                        # -- End function
        .globl  main                    # -- Begin function main
        .p2align        2
        .type   main,@function
main:                                   # @main
# %bb.0:                                # %top
        addi    sp, sp, -16
        sw      ra, 12(sp)
        mv      a3, a1
        mv      a1, a0
        lui     a0, %hi(_ZN4main4main17hfe98083a4c87500fE)
        addi    a0, a0, %lo(_ZN4main4main17hfe98083a4c87500fE)
        srai    a2, a1, 31
        call    _ZN3std2rt10lang_start17hd1a40614a9e43128E
        lw      ra, 12(sp)
        addi    sp, sp, 16
        ret
.Lfunc_end10:

一方で、オリジナルのRISC-VバックエンドMYRISCVXで実行してみた。

./bin/llc -debug -march=myriscvx32 -mcpu=simple32 /home/msyksphinz/work/llvm/myriscvx-tests/rust/rust_simple/src/main.bc -o main.s

スタックフレームの処理でAssertionが落ちてしまった。。。何か機能実装が足りないみたいだ。。。原因を調査する。

Function : _ZN4core3fmt9Arguments6new_v117h9482ffdd5f1340abE
<--------->
$a1 = LW %stack.0._4, killed $a1 :: (dereferenceable load 4 from %ir.8, align 8)
FrameIndex : 0
spOffset   : -16
stackSize  : 16
llc: /home/msyksphinz/work/llvm/llvm-myriscvx/include/llvm/CodeGen/MachineOperand.h:527: int64_t llvm::MachineOperand::getImm() const: Assertion `isImm() && "Wrong MachineOperand accessor"' failed.
Stack dump:

高性能プロセッサの分岐予測のサーベイ論文を読んで分岐予測について学ぶ (8. ニューラル分岐予測器1)

プロセッサアーキテクチャについて再度復習その8。今回からニューラル分岐予測器。ニューラルネットを用いた分岐予測器。

読んでいるのはA Survey of Techniques for Dynamic Branch Predictionという論文で、新規技術の解説ではないのだが、現在の有名どころの分岐予測技術についてまんべんなく解説してくれている論文だ。

  • A Survey of Techniques for Dynamic Branch Prediction
    • Sparsh Mittal

https://arxiv.org/pdf/1804.00261.pdf


7. ニューラル分岐予測器と実装テクニック

ニューラル分岐予測器に関しては、 - パーセプトロン[48] - バックプロパゲーション[28、52、53] - 学習ベクトル量子化[52] - その他のニューラル分岐予測器 [28]

を使った分岐予測器が提案されている。

7.1 Neural BPs

Jimenezらの提案[48]。各分岐を表現するために単層パーセプトロン、単純NNを使用するパーセプトロンベースの分岐予測器を提案する。パーセプトロンは、異なる分岐間の相関度を示す複数の重み($w$)を格納するベクトルだ。前の分岐(1 =成立、-1 =成立しない)の結果がパーセプトロンへの入力になる。常に1である入力$x_0$はバイアス入力を提供する。出力$Z$はお元入力のない席として計算され、$Z = w_0 + \sum w_ix_i$である。$Z$が正か負かによって、分岐が成立と予測するか、不成立と予測するかが決まる。分岐結果によって、要素の重みはそれぞれ増加/減少する。図31に、パーセプトロン分岐予測器の機能の例を示す。

f:id:msyksphinz:20190805004135p:plain
本論文より抜粋

2レベル分岐予測器のサイズは、分岐履歴レジスタの長さに応じて指数関数的に大きくなるが、パーセプトロン分岐予測器のサイズは履歴の長さに対して直線的にしか増加しない[28]。したがって、これまでの分岐予測器に比較してはるかに長い履歴を保存することができる。

それらの分岐予測器は、多くの「線形分離」可能な分岐を持つアプリケーションに適している。

超平面を使用してすべての偽のインスタンスをすべての真のインスタンスから分離することができるような$n+1$の重みの値が見つかる場合、変数$x_{1...n}$に対するブール関数は線形分離可能だ。

  • AND関数は線形分離可能であるのに対し、
  • XOR関数は線形分離はできない。

これを図32(a) - (b)に示す。パーセプトロン分岐予測器はAND関数ではうまく機能するが、XOR関数ではうまくいかない。したがって、線形分離不可能な関数の場合、パーセプトロン分岐予測器の精度は低くなるが、以前の分岐予測器は十分な時間が与えられれば任意のブール関数を学習できる。したがって、一般的なアプリケーションでは、それらの分岐予測器は単一の分岐予測器としてではなく、ハイブリッド分岐予測器の構成要素として有用だ。彼らの分岐予測器のもう一つの制限は、その長い待ち時間だ。

f:id:msyksphinz:20190805004159p:plain
本論文より抜粋

Jimenez[49]は、パスベースのニューラル分岐予測器を提案した。これは「先行パイプライン(ahead pipelining)」アプローチを使用して待ち時間を短縮する。彼らの分岐予測器は、分岐アドレスだけではなく、分岐に至るパスに応じてその重みベクトルを選択する。これはより高い精度を達成するのに役立つ。パーセプトロン分岐予測器と同様に、それらのパスベースの分岐予測器は重みベクトルの行列を維持する。分岐を予測するには、図33に示すように、重みベクトルが読み込まれ、予測を行うためにその偏りの重みだけをRunning Sumに追加する必要がある。このRunning Sumは、複数の前の分岐に対して更新される。したがって、それらの先行パイプライン化アプローチは、時間の経過と共に計算をずらすので、予測が行われる前であっても計算を開始することができる。予測は複数サイクルで完了することができるため、分岐予測器オーバーライドの必要性がなくなる。彼らのアプローチの限界はそれが正確さを減らすことができるその重みを選ぶために分岐のPCを使わないことだ。また、先読みパイプライン化は、古い/部分的な情報で予測プロセスを開始し、予測プロセスで最新の情報を継続的に混合することによって機能するが、先読みパイプライン化アプローチでは限られた新しい情報しか使用できない[23]。さらに、分岐の誤った予測では、大量のプロセッサ状態をチェックポイントにロールバックする必要がある。

Jimenez[51]は、パーセプトロンと経路ベースの分岐予測器の一般化である区分線形分岐予測器を示している[48, 49]。すべての分岐Bについて、それらの分岐予測器はBに至るすべてのパスのコンポーネントを追跡する。それはBの結果と一致するために履歴内の特定の位置での分岐の可能性を追跡する。予測は、現在のパスのすべてのコンポーネントの相関を集約することによって行われる。全体として、それらの分岐予測器は、予測された非分岐を予測された分岐から分離するために現在の分岐への各経路に対して1つずつ、複数の線形関数を生成する。したがって、それらの分岐予測器は、図32(c)に示すように、線形的に分離不可能な分岐に対して有効だ。ストレージの予算や待ち時間に制約がないと仮定すると、それらの分岐予測器は他の分岐予測器よりも高い予測精度を達成する。彼らはさらに先読みパイプラインと小さな履歴長の値を使用する彼らの分岐予測器の実用的な実装を提案する。また、パス履歴における分岐先アドレスおよび分岐先アドレスは、それぞれモジュロN, Mとして記憶される。パーセプトロンベースの分岐予測器 [48]は分岐(M = 1)に単一の線形関数を利用し、パスベースのニューラル分岐予測器 [49]はすべての分岐(N = 1)を予測するために単一のグローバル区分線形関数を利用する。区分線形分岐予測器の特殊な場合がある[51]。彼らは彼らの分岐予測器の実用的なバージョンも他の予測器よりも優れていることを示している。

Jimenezら[28]の提案。単層パーセプトロンベースの分岐予測器とは異なり、バックプロパゲーションベースの分岐予測器を備えた多層パーセプトロンは線形分離不可能な関数を学習できるため、パーセプトロン分岐予測器よりも高い「漸近的」精度を提供することが期待される。しかし、実際には、逆伝播ニューラル分岐予測器はトレーニングと予測の両方に非常に長い時間がかかるため、精度とパフォーマンスが低くなり、逆伝播ベースの分岐予測器は実行不可能で無効になる[53]。

Tarjanらの提案[23]。gshare, パスベース、パーセプトロン分岐予測器のアイデアを使用した、ハッシュ化パーセプトロン分岐予測器を提案する。この手法では、パーセプトロン分岐予測器は、分岐の相関を測定するためにそれぞれの重みを使用しており、これは使用する履歴の量がが増えるとテーブルと加算器の数が線形に増加し、またそれぞれのカウンタは十分に大きく取り、重みがいくつかの他の重みをオーバライドすることができるようにする。グローバル分岐履歴のセグメントをPCとXORすることによって、複数の分岐に同じ重みを割り当てる分岐予測器を提案している。グローバル分岐/パス履歴との単一の一致を行う代わりに、それらは複数の部分一致を実行するためにそれを複数のセグメントに分割する。さらに、パス履歴を使用する代わりに、分岐PCのみを使用する。ハッシュインデックスでは、すべてのテーブルが小さなgshare 分岐予測器として機能するため、それらの分岐予測器は同じ重みにマッピングされた線形的に分離不可能な分岐も予測できる。また、パスベースの分岐予測器と比較して、分岐予測器は同じ履歴の長さに使用する重み数を少なくする。これにより、相関のない複数の重みが強い相関を持つ1つの重みを上書きする可能性が低くなる。グローバルまたはパスベースの分岐予測器と比較して、それらの分岐予測器はチェックポイントが取られる状態の量を減らす短いパイプラインを持っている。 分岐予測器の待ち時間を減らすために、彼らは先送りパイプラインを使用する。パスベースおよびグローバルニューラル分岐予測器と比較して、加算器の数を減らしながら、分岐予測器は精度を大幅に向上させる。

f:id:msyksphinz:20190805005438p:plain
本論文より抜粋

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