FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (47. 浮動小数点レジスタの定義)

f:id:msyksphinz:20190425001356p:plain

前章まででMYRISCVXの整数命令に関してLLVMバックエンドへの実装を進めてきた。 これだけでも随分と完成度が上がってきたが、まだ足りていないものがある。その一つとして、浮動小数点命令のサポートだ。

RISC-Vでは現状では2種類の浮動小数点命令が定義されている。

それぞれ、接尾語に.S.Dが付けられており、たとえば、

  • fmadd.s rd,rs1,rs2,rs3 : 単精度FMA演算命令 f[rd] = f[rs1]×f[rs2]+f[rs3]
  • fmadd.d rd,rs1,rs2,rs3 : 倍精度FMA演算命令 f[rd] = f[rs1]×f[rs2]+f[rs3]

などが定義されている。ここでは、これらの浮動小数点命令を追加してみる。

浮動小数レジスタを定義

まずは、浮動小数レジスタを定義しないと始まらない。 RISC-Vの浮動小数レジスタは、実体としては単精度版と倍精度版が共有されている。 つまり、倍精度をサポートしているRV64Dのアーキテクチャならば64ビットのレジスタが32本あり、下位の32ビットが単精度レジスタと共有されている。 一方、単精度をサポートしているRV32Fのみのサポートで、RV64Dをサポートしていないようなアーキテクチャならば、倍精度命令はそのままでは実行できない。 エミュレートなり、別の方法を模索する必要がある。

ここではRV32F用の単精度レジスタ、RV64D用の倍精度レジスタを定義する。しかし、これは共有関係にあるので、以下のような実装方針を取る。

  1. 単精度浮動小数レジスタ32本をこれまで通り定義する。
  2. 倍精度浮動小数レジスタ32本を定義するが、64ビットのうち下位の32ビットを単精度レジスタと共有するようにサブレジスタ指定を行う。

このようなサブレジスタによる共有は、冷静に考えてみると必須の機能だ。 X86ではAX, EAXレジスタなどのレジスタフィールドが共有されれているレジスタは沢山あるので、このようなレジスタ共有の機能があるのは当然だ。

f:id:msyksphinz:20190801225023p:plain
RV32FとRV64Dのレジスタをサブレジスタとして定義する仕組み

というわけで、まずは単精度浮動小数レジスタを定義する。MYRISCVXRegisterInfo.tdに以下の定義を追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td
// Floating point registers
let Namespace = "MYRISCVX" in {
  def F0_S  : MYRISCVXGPRReg< 0,  "f0", ["ft0" ]>, DwarfRegNum<[32]>;
  def F1_S  : MYRISCVXGPRReg< 1,  "f1", ["ft1" ]>, DwarfRegNum<[33]>;
  def F2_S  : MYRISCVXGPRReg< 2,  "f2", ["ft2" ]>, DwarfRegNum<[34]>;
...
  def F29_S : MYRISCVXGPRReg<29, "f29", ["ft9" ]>, DwarfRegNum<[61]>;
  def F30_S : MYRISCVXGPRReg<30, "f30", ["ft10"]>, DwarfRegNum<[62]>;
  def F31_S : MYRISCVXGPRReg<31, "f31", ["ft11"]>, DwarfRegNum<[63]>;
}
...
// Single Floating Point Register Class
def FPR_S : RegisterClass<"MYRISCVX", [f32], 32, (add
  F0_S , F1_S,  F2_S,  F3_S,  F4_S,  F5_S,  F6_S,  F7_S,
  F8_S , F9_S,  F10_S, F11_S, F12_S, F13_S, F14_S, F15_S,
  F16_S, F17_S, F18_S, F19_S, F20_S, F21_S, F22_S, F23_S,
  F24_S, F25_S, F26_S, F27_S, F28_S, F29_S, F30_S, F31_S
  )>;

MYRISCVXGPRRegクラスを使用して単精度レジスタを32本定義する。 整数レジスタの定義とあまり変わらないが、DwarfRegNumの定義のみ少し注意が必要だ。 ‘DwarfRegNumGCCGDBレジスタを参照するための一意に決められたレジスタ番号なので、整数レジスタと番号が被ってはいけない。 したがって、単精度レジスタ32本では32-63までの番号を使っている。

次に、倍精度浮動小数レジスタを定義する。上記で説明した通り、サブレジスタ指定を使って単精度レジスタを包含する。 このために、MYRISCVXFPR_Dレジスタクラスを作成する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td
  def sub_32 : SubRegIndex<32>;
  // MYRISCVX Double Floating-Point Registers
  class MYRISCVXFPR_D<bits<5> Enc, list<string> alt,
                      MYRISCVXGPRReg subreg> : Register<""> {
    let HWEncoding{4-0} = Enc;
    let SubRegs  = [subreg];
    let SubRegIndices = [sub_32];
    let AsmName  = subreg.AsmName;
    let AltNames = subreg.AltNames;
  }

要点としては以下だ。

  • MYRISCVXFPR_DRegisterクラスを継承する。ただし、その名前AsmRegisterはとりあえず空白""とする。
  • ハードウェエンコーディングHWEncodingは指定したものをそのまま使用する。
  • サブレジスタとしてSubRegsを指定する。これは配列を指定できるので、[subreg]として配列を指定する。subregは倍精度浮動小数レジスタ定義時に指定する。
  • レジスタAsmNameと代替レジスタAltNamesをここで指定する。これは、subregのものをそのまま使用する。

MYRISCVXFPR_Dクラスを利用して、倍精度レジスタを定義する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td
...
  def F0_D  : MYRISCVXFPR_D< 0, ["ft0" ], F0_S >, DwarfRegNum<[64]>;
  def F1_D  : MYRISCVXFPR_D< 1, ["ft1" ], F1_S >, DwarfRegNum<[65]>;
  def F2_D  : MYRISCVXFPR_D< 2, ["ft2" ], F2_S >, DwarfRegNum<[66]>;
...
  def F29_D : MYRISCVXFPR_D<29, ["ft9" ], F29_S>, DwarfRegNum<[93]>;
  def F30_D : MYRISCVXFPR_D<30, ["ft10"], F30_S>, DwarfRegNum<[94]>;
  def F31_D : MYRISCVXFPR_D<31, ["ft11"], F31_S>, DwarfRegNum<[95]>;

32本分の倍精度レジスタを定義し、それぞれサブレジスタとして単精度レジスタを定義した。

ただし、これだけだとコンパイル時にエラーとなる。これは、現状の設定だとレジスタAltNameが重複する定義を許さないためだ。 現状では単精度レジスタf0-f31、倍精度レジスタf0-f31という名前で定義されており、レジスタ名が重複している。

LLVM ERROR: Had duplicate keys to match on

そこで、MYRISCVX.tdレジスタの重複を許可する設定を追加する。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVX.td
def MYRISCVXAsmParser : AsmParser {
  let ShouldEmitMatchRegisterAltName = 1;
  let AllowDuplicateRegisterNames = 1;
}

def MYRISCVX : Target {
  let InstructionSet = MYRISCVXInstrInfo;
  let AssemblyParsers = [MYRISCVXAsmParser];
}

AllowDuplicateRegisterNamesを1に設定することで、レジスタ名が重複してもエラーを出さない。これで最後までコンパイルが通るようになる。