前章までで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に設定することで、レジスタ名が重複してもエラーを出さない。これで最後までコンパイルが通るようになる。
