では、まずは簡単な浮動小数点命令の実装から入る。 演算命令を定義するところから始めてもよいが、そのまえに必ずロードストア命令が必要になるので、ロードストア命令から入る。
RISC-Vでは、単精度浮動小数点用のFLW / FSW、倍精度浮動小数点用のFLD / FSD命令が定義されている。まずはこれらを定義する。
整数ロードストア命令のためにTarget DescriptionのクラスとしてLoadM32 / StoreM32を定義していた。
このクラスは取り扱うデータを整数に限定(つまりレジスタはGPRのみ)にしていたので、一般化して任意のレジスタクラスに対してデータを定義できるように変更する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td(差分)
//@ 32-bit load.
-multiclass LoadM32<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag OpNode,
+multiclass LoadM32<bits<7> opcode, bits<3> funct3, string instr_asm,
+ RegisterClass RC, PatFrag OpNode,
bit Pseudo = 0> {
- def #NAME# : LoadM<opcode, funct3, instr_asm, OpNode, GPR, Pseudo>;
+ def #NAME# : LoadM<opcode, funct3, instr_asm, OpNode, RC, Pseudo>;
}
// 32-bit store.
-multiclass StoreM32<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag OpNode,
+multiclass StoreM32<bits<7> opcode, bits<3> funct3, string instr_asm,
+ RegisterClass RC, PatFrag OpNode,
bit Pseudo = 0> {
- def #NAME# : StoreM<opcode, funct3, instr_asm, OpNode, GPR, Pseudo>;
+ def #NAME# : StoreM<opcode, funct3, instr_asm, OpNode, RC, Pseudo>;
}
これまでGPRと決め打ちしていたレジスタクラスを、RCとして一般化した。
これに伴い、LoadM32 / StoreM32を使用するクラスの定義にレジスタクラスを追加する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td(差分)
@@ -495,14 +500,14 @@ let isReturn=1, isTerminator=1, hasDelaySlot=0, isBarrier=1, hasCtrlDep=1 in /// Load and Store Instructions /// aligned -defm LW : LoadM32 <0b0000011, 0b010, "lw", load_a>; -defm SW : StoreM32<0b0100011, 0b010, "sw", store_a>; -defm LB : LoadM32 <0b0000011, 0b000, "lb", sextloadi8_a>; -defm LBU : LoadM32 <0b0000011, 0b100, "lbu", zextloadi8_a>; -defm SB : StoreM32<0b0100011, 0b000, "sb", truncstorei8_a>; -defm LH : LoadM32 <0b0000011, 0b001, "lh", sextloadi16_a>; -defm LHU : LoadM32 <0b0000011, 0b101, "lhu", zextloadi16_a>; -defm SH : StoreM32<0b0100011, 0b001, "sh", truncstorei16_a>; +defm LW : LoadM32 <0b0000011, 0b010, "lw", GPR, load_a>; +defm SW : StoreM32<0b0100011, 0b010, "sw", GPR, store_a>; +defm LB : LoadM32 <0b0000011, 0b000, "lb", GPR, sextloadi8_a>; +defm LBU : LoadM32 <0b0000011, 0b100, "lbu", GPR, zextloadi8_a>; +defm SB : StoreM32<0b0100011, 0b000, "sb", GPR, truncstorei8_a>; +defm LH : LoadM32 <0b0000011, 0b001, "lh", GPR, sextloadi16_a>; +defm LHU : LoadM32 <0b0000011, 0b101, "lhu", GPR, zextloadi16_a>; +defm SH : StoreM32<0b0100011, 0b001, "sh", GPR, truncstorei16_a>;
これに追加する形で、FLW / FSW / FLD / FSDの定義を追加する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td
// Floating Point Load Store defm FLW : LoadM32 <0b0000111, 0b010, "flw", FPR_S, load_a >; defm FSW : StoreM32<0b0100111, 0b010, "fsw", FPR_S, truncstoref32_a>; defm FLD : LoadM32 <0b0000111, 0b011, "fld", FPR_D, load_a >; defm FSD : StoreM32<0b0100111, 0b011, "fsd", FPR_D, truncstoref64_a>;
ここでは、store_aではなくtruncstoref32_aおよびtruncstoref64_aというノードを使用している。
store_aはstoreノードを継承しているのだが、storeノードは整数用のノードなので、浮動小数点用の別のノードを使用する必要がある。

llvm-myriscvx/include/llvm/Target/TargetSelectionDAG.td
def store : PatFrag<(ops node:$val, node:$ptr),
(unindexedstore node:$val, node:$ptr)> {
let IsStore = 1;
let IsTruncStore = 0;
}
...
// 浮動小数点用のtruncstoreが存在する。
def truncstoref32 : PatFrag<(ops node:$val, node:$ptr),
(truncstore node:$val, node:$ptr)> {
let IsStore = 1;
let MemoryVT = f32;
}
def truncstoref64 : PatFrag<(ops node:$val, node:$ptr),
(truncstore node:$val, node:$ptr)> {
let IsStore = 1;
let MemoryVT = f64;
}
これで命令の定義は完了したが、これだけでは足りない。 DAGのパタンに合わせて命令を生成する記述を追加する。これも整数命令を元に追加する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXRegisterInfo.td
def : Pat<(f32 (load GPR:$rs1)) , (FLW GPR:$rs1, 0) >; def : Pat<(f32 (load addr_fi:$rs1)) , (FLW addr_fi:$rs1, 0) >; def : Pat<(f32 (load (add GPR:$rs1, simm12:$simm12))) , (FLW GPR:$rs1, $simm12) >; def : Pat<(f32 (load (add addr_fi:$rs1, simm12:$simm12))), (FLW addr_fi:$rs1, $simm12) >; def : Pat<(store FPR_S:$rs2, GPR:$rs1) , (FSW FPR_S:$rs2, GPR:$rs1, 0) >; def : Pat<(store FPR_S:$rs2, addr_fi:$rs1) , (FSW FPR_S:$rs2, addr_fi:$rs1, 0) >; def : Pat<(store FPR_S:$rs2, (add GPR:$rs1, simm12:$simm12)) , (FSW FPR_S:$rs2, GPR:$rs1, simm12:$simm12) >; def : Pat<(store FPR_S:$rs2, (add addr_fi:$rs1, simm12:$simm12)), (FSW FPR_S:$rs2, addr_fi:$rs1, simm12:$simm12) >; def : Pat<(f64 (load GPR:$rs1)) , (FLD GPR:$rs1, 0) >; def : Pat<(f64 (load addr_fi:$rs1)) , (FLD addr_fi:$rs1, 0) >; def : Pat<(f64 (load (add GPR:$rs1, simm12:$simm12))) , (FLD GPR:$rs1, $simm12) >; def : Pat<(f64 (load (add addr_fi:$rs1, simm12:$simm12))), (FLD addr_fi:$rs1, $simm12) >; def : Pat<(store FPR_D:$rs2, GPR:$rs1) , (FSD FPR_D:$rs2, GPR:$rs1, 0) >; def : Pat<(store FPR_D:$rs2, addr_fi:$rs1) , (FSD FPR_D:$rs2, addr_fi:$rs1, 0) >; def : Pat<(store FPR_D:$rs2, (add GPR:$rs1, simm12:$simm12)) , (FSD FPR_D:$rs2, GPR:$rs1, simm12:$simm12) >; def : Pat<(store FPR_D:$rs2, (add addr_fi:$rs1, simm12:$simm12)), (FSD FPR_D:$rs2, addr_fi:$rs1, simm12:$simm12) >;
では、ここまで来たらLLVMのビルドを行い、テストプログラムを動かしてみる。以下のような簡単なコードを考える。
fp_mem.cpp
void fp_memoy(float *dst, float *src) { *dst = *src; } void dp_memoy(double *dst, double *src) { *dst = *src; }
これをコンパイルしてみる。
./bin/clang -O0 fp_mem.cpp -emit-llvm ./bin/llc -filetype=asm fp_mem.bc -mcpu=simple32 -march=myriscvx32 -target-abi=lp64 -o -
float側のアセンブリだ。正しくflwとfswが生成されていることが分かる。
_Z8fp_memoyPfS_: # %bb.0: # %entry addi x2, x2, -16 sw x10, 8(x2) sw x11, 0(x2) lw x10, 0(x2) flw f0, 0(x10) lw x10, 8(x2) fsw f0, 0(x10) addi x2, x2, 16 ret
double側のアセンブリだ。正しくfldとfsdが生成されていることが分かる。
_Z8dp_memoyPdS_: # %bb.0: # %entry addi x2, x2, -16 sw x10, 8(x2) sw x11, 0(x2) lw x10, 0(x2) fld f0, 0(x10) lw x10, 8(x2) fsd f0, 0(x10) addi x2, x2, 16 ret