では、まずは簡単な浮動小数点命令の実装から入る。 演算命令を定義するところから始めてもよいが、そのまえに必ずロードストア命令が必要になるので、ロードストア命令から入る。
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