前章までで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用の倍精度レジスタを定義する。しかし、これは共有関係にあるので、以下のような実装方針を取る。
このようなサブレジスタによる共有は、冷静に考えてみると必須の機能だ。 X86ではAX, EAXレジスタなどのレジスタフィールドが共有されれているレジスタは沢山あるので、このようなレジスタ共有の機能があるのは当然だ。
というわけで、まずは単精度浮動小数点レジスタを定義する。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
の定義のみ少し注意が必要だ。
‘DwarfRegNum
はGCCやGDBがレジスタを参照するための一意に決められたレジスタ番号なので、整数レジスタと番号が被ってはいけない。
したがって、単精度レジスタ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_D
はRegister
クラスを継承する。ただし、その名前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に設定することで、レジスタ名が重複してもエラーを出さない。これで最後までコンパイルが通るようになる。