LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。
資料としては Tutorial: Creating an LLVM Backend for the Cpu0 Architecture を使用している。やっとChapter-4だ。
DIV命令は演算のコストが大きくなってしまうので、LLVMはsrem命令を乗算に置き換えてしまう。
int b = 11; b = (b + 1) % 12
上記の演算は、 以下のように変換される。正直この変換は意味が分からないが、等価なのであろう。。。
(b + 1) % 12 = 0xC * 0x2AAAAAAB = 0x2_00000004 A = 0x2 >> 1 = 1 B = 0x1 << 31 = 0 C = A + B = 1 D = C * 12 = 1 * 12 = 12 ANS = D - 12 = 0
以下のようなデータフローが生成される。
ちなみに最適化前。通常のsremが使用されている。
一方でMipsの方式を使用すると、以下のようになるらしい。MIPSはHILOレジスタを使用するので、MUL命令とMFHIを使用すればよい。
lui $1, 20164 addiu $2, $4, 1 ori $1, $1, 60495 mult $2, $1 mfhi $1 srl $3, $1, 31 sra $1, $1, 2 addu $1, $1, $3 sll $3, $1, 1 addu $3, $3, $1 sll $1, $1, 4 subu $1, $3, $1 jr $ra addu $2, $2, $1
一方で、volatileを使用すると最適化が抑制される。
int test_mod() { int b = 11; volatile int a = 12; b = (b+1)%a; return b; }
addi x10, x10, 1 lw x11, 0(x2) rem x10, x10, x11 sw x10, 4(x2) lw x10, 4(x2) addi x2, x2, 8 ret
ローテート命令の実装 以下をコンパイルすると、エラーが出てしまう。RISC-Vにはローテート命令は実装されていないので、これはどうするか問題だ。
$ ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm ch4_1_rotate.bc -o ch4_1_rotate.myriscvx.s
LLVM ERROR: Cannot select: t18: i32 = rotl t6, Constant:i32<30> t6: i32,ch = load<(dereferenceable load 4 from %ir.a)> t5, FrameIndex:i32<0>, undef:i32 t2: i32 = FrameIndex<0> t4: i32 = undef t7: i32 = Constant<30> In function: _Z16test_rotate_leftv
本物のRISC-Vサポートはどのような実装になっているのか調べてみた。
lib/Target/Mips/MipsISelLowering.cpp
setOperationAction(ISD::ROTL, MVT::i32, Expand); setOperationAction(ISD::ROTL, MVT::i64, Expand); ... if (!Subtarget.hasMips32r2()) setOperationAction(ISD::ROTR, MVT::i32, Expand); if (!Subtarget.hasMips64r2()) setOperationAction(ISD::ROTR, MVT::i64, Expand);
setOperationAction()
というのはどういうことだろう?とりあえず自分の実装にも加えてみる。
次に論理命令を追加する。これがないと最終的に上記のch4_1_rotate
は生成できない。
-def ORI : ArithLogicI<0b0010011, 0b110, "ori", or, uimm12, immZExt12, GPR>; +def XORI : ArithLogicI<0b0010011, 0b110, "xori", xor, uimm12, immZExt12, GPR>; +def ORI : ArithLogicI<0b0010011, 0b110, "ori", or, uimm12, immZExt12, GPR>; +def ANDI : ArithLogicI<0b0010011, 0b111, "andi", and, uimm12, immZExt12, GPR>; + def LUI : ArithLogicU<0b0110111, "lui", simm20, immSExt12>; def ADD : ArithLogicR<0b0110011, 0b000, "add", add, GPR>; def SUB : ArithLogicR<0b0110011, 0b000, "sub", sub, GPR>; -// sra is IR node for ashr llvm IR instruction of .bc -def SRL : shift_rotate_reg<0b0110011, 0b0000000, 0b101, 0x0, "srl", srl, GPR>; def SLL : shift_rotate_reg<0b0110011, 0b0000000, 0b001, 0x0, "sll", shl, GPR>; +def AND : ArithLogicR<0b0110011, 0b111, "and", and, GPR>; +def SRL : shift_rotate_reg<0b0110011, 0b0000000, 0b101, 0x0, "srl", srl, GPR>; def SRA : shift_rotate_reg<0b0110011, 0b0100000, 0b101, 0x0, "sra", sra, GPR>; +def OR : ArithLogicR<0b0110011, 0b110, "or", or, GPR>; +def XOR : ArithLogicR<0b0110011, 0b100, "xor", xor, GPR>;
とりあえずこれで命令がローテート操作が命令出力できるようになった。一安心。
次に、比較命令の実装を行っていく。 比較命令のためには、Predicateのためのオペレーションとしてsltiとかsltuとかが使用できるが、通常のSDNodeではなく、PatFragという型で定義しなければならないらしい。 以下のフォーマットを作成した。
lib/Target/Mips/MipsISelLowering.cpp
// SetCC class SetCC_R<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag cond_op, RegisterClass RC> : FI<opcode, funct3, (outs GPR:$ra), (ins RC:$rb, RC:$rc), !strconcat(instr_asm, "\t$ra, $rb, $rc"), [(set GPR:$ra, (cond_op RC:$rb, RC:$rc))], IIAlu> { let isReMaterializable = 1; } class SetCC_I<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag cond_op, Operand imm, PatLeaf imm_type, RegisterClass RC> : FI<opcode, funct3, (outs GPR:$ra), (ins RC:$rb, imm:$imm12), !strconcat(instr_asm, "\t$ra, $rb, $imm12"), [(set GPR:$ra, (cond_op RC:$rb, imm_type:$imm12))], IIAlu> { let isReMaterializable = 1; }
これに基づいて比較命令を実装した。これで一応LLVMがコンパイルできるようになった。比較処理はまだ命令生成できないけど。
def SLTI : SetCC_I<0b0010011, 0b010, "slti", setlt, simm12, immSExt12, GPR>; def SLTIU : SetCC_I<0b0010011, 0b011, "sltiu", setult, simm12, immSExt12, GPR>; def SLT : SetCC_R<0b0110011, 0b010, "slt", setlt, GPR>; def SLTU : SetCC_R<0b0110011, 0b011, "sltu", setult, GPR>;