LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。
第8章は制御構文の追加。手始めにまずは分岐命令を実装していく。 分岐命令を生成させるためには、分岐命令向けのクラステンプレートを作って命令を当てはめていく。
lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Branch insntructions with 2 register operands. class CBranch12<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag cond_op, RegisterClass RC> : FB<opcode, funct3, (outs), (ins RC:$ra, RC:$rb, brtarget12:$imm12), !strconcat(instr_asm, "\t$ra, $rb, $imm12"), [], IIAlu> { let isBranch = 1; let isTerminator = 1; }
ここで、(ins RC:$ra, RC:$rb, brtarget12:$imm12)
に当てはめて命令が生成されるのだが、brtarget12
というクラスを定義する必要がある。
これは以下のように定義される。OtherVT
というは何だ?というかこのあたりの日本語の情報がなくて非常に苦労した。。。
lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// BEQ, BNE def brtarget12 : Operand<OtherVT> { let EncoderMethod = "getBranch12TargetOpValue"; let OperandType = "OPERAND_PCREL"; let DecoderMethod = "DecodeBranch12Target"; }
getBranch12TargetOpValue
はとりあえず関数だけ定義して中身は省略する。
lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCCodeEmitter.cpp
unsigned MYRISCVXMCCodeEmitter:: getBranch12TargetOpValue(const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const { return 0; }
次に、分岐命令をすべて定義していく。RISC-Vには、以下の命令が定義されている。CondOp
をそれぞれ使用して、Predicateできるような形を作っていく。
lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def BEQ : CBranch12<0b1100011, 0b000, "beq" , seteq, GPR>; def BNE : CBranch12<0b1100011, 0b001, "bne" , setne, GPR>; def BLT : CBranch12<0b1100011, 0b100, "blt" , setlt, GPR>; def BGE : CBranch12<0b1100011, 0b101, "bge" , setge, GPR>; def BLTU : CBranch12<0b1100011, 0b110, "bltu", setult, GPR>; def BGEU : CBranch12<0b1100011, 0b111, "bgeu", setuge, GPR>;
次に、LLVM IRから命令を生成させるためのパターンを定義していく。
lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// brcond for slt instruction multiclass BrcondPatsSlt<RegisterClass RC, Instruction BEQOp, Instruction BNEOp, Instruction SLTOp, Instruction SLTuOp, Instruction SLTiOp, Instruction SLTiuOp, Register ZEROReg> { def : Pat<(brcond (i32 (setne RC:$lhs, 0)), bb:$dst), (BNEOp RC:$lhs, ZEROReg, bb:$dst)>; def : Pat<(brcond (i32 (seteq RC:$lhs, 0)), bb:$dst), (BEQOp RC:$lhs, ZEROReg, bb:$dst)>; def : Pat<(brcond (i32 (seteq GPR:$lhs, GPR:$rhs)), bb:$dst), (BEQOp RC:$lhs, RC:$rhs, bb:$dst)>; def : Pat<(brcond (i32 (setueq RC:$lhs, RC:$rhs)), bb:$dst), (BEQOp RC:$lhs, RC:$rhs, bb:$dst)>; ... } defm : BrcondPatsSlt<GPR, BEQ, BNE, SLT, SLTU, SLTI, SLTIU, ZERO>;
次に、条件なし分岐命令だ。これが無いと分岐命令の条件不成立側が処理できない。
実際にはこのパターンは不十分で、JAL命令は戻り先アドレスを格納できるのだが、それを定義していない(outs
が空)。
そこを実装し始めると面倒なので、とりあえず条件不成立の時にジャンプできる最低限のパタンだけ追加する。
lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
class UncondBranch<bits<7> opcode, string instr_asm>: FJ<opcode, (outs), (ins brtarget20:$addr), !strconcat(instr_asm, "\t$addr"), [(br bb:$addr)], IIAlu> { let isBranch = 1; let isTerminator = 1; let isBarrier = 1; let hasDelaySlot = 0; } let isCall = 1 in def JAL : UncondBranch<0b1101111, "jal">;
さらに、br_cc
のパタンも追加しないとうまくコンパイルできないらしい。
lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
// Branch Instructions setOperationAction(ISD::BR_CC, MVT::i32, Expand); setOperationAction(ISD::BR_CC, MVT::f32, Expand); setOperationAction(ISD::BR_CC, MVT::f64, Expand);
これで以下のプログラムをコンパイルしてアセンブリを出力した。
int test_ifctrl() { unsigned int a = 0; if (a == 0) { a++; // a = 1 } return a; }
./bin/clang -c -target mips ../lbdex/input/ch8_1_1.cpp -emit-llvm ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm ch8_1_1.bc -o -
正しく命令が生成できたようだ。
# %bb.0: # %entry addi x2, x2, -8 sw x0, 4(x2) lw x10, 4(x2) bne x10, x0, $BB0_2 jal $BB0_1 $BB0_1: # %if.then lw x10, 4(x2) addi x10, x10, 1 sw x10, 4(x2) jal $BB0_2 $BB0_2: # %if.end lw x10, 4(x2) addi x2, x2, 8 ret