制御フロー生成
いよいよLLVMバックエンドの醍醐味、制御構文の生成に入る。if文やfor文などの制御構文を生成できるようにしたい。
まずは、以下のような簡単なCプログラムをClangに入力し、LLVM IRを生成してみる。どのような制御IRが必要になるのかを確認する。
int test_ifctrl() { unsigned int a = 0; if (a == 0) { a++; // a = 1 } return a; }
./bin/clang --target=mips-unknown-elf ../lbdex/input/ch6_1.cpp -c -emit-llvm ./bin/llvm-dis ch8_1_1.bc -o -
; Function Attrs: noinline nounwind optnone define dso_local i32 @_Z11test_ifctrlv() #0 { entry: %a = alloca i32, align 4 store i32 0, i32* %a, align 4 %0 = load i32, i32* %a, align 4 %cmp = icmp eq i32 %0, 0 br i1 %cmp, label %if.then, label %if.end if.then: ; preds = %entry %1 = load i32, i32* %a, align 4 %inc = add i32 %1, 1 store i32 %inc, i32* %a, align 4 br label %if.end if.end: ; preds = %if.then, %entry %2 = load i32, i32* %a, align 4 ret i32 %2 }
まずは、無条件でジャンプする命令から追加する。これはif.then
節が有効である場合、最後にbr label %if.end
によりif.end
によりジャンプする場合に必要な命令だ(実際には不要なジャンプだが、これはPassを追加することで後で削除する)。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Unconditional Branch class JumpOffset<bits<7> opcode, string instr_asm, RegisterClass RC>: MYRISCVX_J<opcode, (outs RC:$rd), (ins brtarget20:$addr), !strconcat(instr_asm, "\t$rd, $addr"), [], IIAlu> { let isBranch = 1; let isTerminator = 1; let isBarrier = 1; let hasDelaySlot = 0; } class JumpOffsetX0<bits<7> opcode, string instr_asm>: JumpOffset<opcode, instr_asm, GPR> { let rd = 0; }
// JAL def brtarget20 : Operand<OtherVT> { let EncoderMethod = "getBranch20TargetOpValue"; let OperandType = "OPERAND_PCREL"; let DecoderMethod = "DecodeBranch20Target"; }
JumpOffsetX0
は即値のジャンプで、JumpOffset
をベースにしている。JumpOffset
はRISC-VのJ形式のフォーマットで、通常は書き込み先の汎用レジスタを取るのだが、J命令の場合は$rd=0となるので、そのためにJumpOffsetX0を定義している。これを使って、J命令を定義する。
def JAL : JumpOffset<0b1101111, "jal", GPR>; def J : JumpOffsetX0<0b1101111, "j">;
これを用いて、ブロックへのジャンプのためのパタンを作成する。
def : Pat<(br bb:$simm20), (J bb:$simm20)>;
br
つまり分岐のパタンを、J命令に割り当てる。br
は以下のように定義されている。
llvm-myriscvx/include/llvm/Target/TargetSelectionDAG.td
def br : SDNode<"ISD::BR" , SDTBr, [SDNPHasChain]>;
次に、比較命令とジャンプを実装する。比較命令は、RISC-Vでは以下の命令を定義する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Branch instructions with 2 register operands. class CondBranch12<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag cond_op, RegisterClass RC> : MYRISCVX_B<opcode, funct3, (outs), (ins RC:$rs1, RC:$rs2, brtarget12:$imm12), !strconcat(instr_asm, "\t$rs1, $rs2, $imm12"), [], IIAlu> { let isBranch = 1; let isTerminator = 1; }
// BEQ, BNE def brtarget12 : Operand<OtherVT> { let EncoderMethod = "getBranch12TargetOpValue"; let OperandType = "OPERAND_PCREL"; let DecoderMethod = "DecodeBranch12Target"; }
def BEQ : CondBranch12<0b1100011, 0b000, "beq" , seteq, GPR>; def BNE : CondBranch12<0b1100011, 0b001, "bne" , setne, GPR>; def BLT : CondBranch12<0b1100011, 0b100, "blt" , setlt, GPR>; def BGE : CondBranch12<0b1100011, 0b101, "bge" , setge, GPR>; def BLTU : CondBranch12<0b1100011, 0b110, "bltu", setult, GPR>; def BGEU : CondBranch12<0b1100011, 0b111, "bgeu", setuge, GPR>;
RISC-Vではレジスタ間条件分岐はB命令形式で定義される。B命令形式を使用したCondBranch12
クラスを作成し、それをベースにして命令を定義する。
JAL命令と条件分岐命令では、それぞれ20ビット、12ビットのオフセット値を使用する。これらのオフセットを扱うためのノードとしてbrtarget20
, brtarget12
を定義した。これらのオペランドを表示するためのメソッドとしてgetBranch20TargetOpValue
およびgetBranch12TargetOpValue
を定義する。
llvm-myriscvx/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCCodeEmitter.cpp
/// getBranch12TargetOpValue - Return binary encoding of the branch /// target operand. If the machine operand requires relocation, /// record the relocation and return zero. unsigned MYRISCVXMCCodeEmitter:: getBranch12TargetOpValue(const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const { const MCOperand &MO = MI.getOperand(OpNo); if (MO.isImm()) { return static_cast<unsigned>(MO.getImm()); } llvm_unreachable("getBranch12TargetOpValue should be imm."); } /// getBranch20TargetOpValue - Return binary encoding of the branch /// target operand. If the machine operand requires relocation, /// record the relocation and return zero. unsigned MYRISCVXMCCodeEmitter:: getBranch20TargetOpValue(const MCInst &MI, unsigned OpNo, SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const { const MCOperand &MO = MI.getOperand(OpNo); if (MO.isImm()) { return static_cast<unsigned>(MO.getImm()); } llvm_unreachable("getBranch20TargetOpValue should be imm."); }
このCondBranch12
クラスは、パラメータとして$rs1, $rs2, $imm12
を取りる。これらの命令を使って条件分岐のパタンを作り上げていく。
llvm-myriscvx/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 RC:$lhs, RC:$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)>; def : Pat<(brcond (i32 (setne RC:$lhs, RC:$rhs)), bb:$dst), (BNEOp RC:$lhs, RC:$rhs, bb:$dst)>; def : Pat<(brcond (i32 (setune RC:$lhs, RC:$rhs)), bb:$dst), (BNEOp RC:$lhs, RC:$rhs, bb:$dst)>; def : Pat<(brcond (i32 (setlt RC:$lhs, RC:$rhs)), bb:$dst), (BNE (SLTOp RC:$lhs, RC:$rhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setult RC:$lhs, RC:$rhs)), bb:$dst), (BNE (SLTuOp RC:$lhs, RC:$rhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setgt RC:$lhs, RC:$rhs)), bb:$dst), (BNE (SLTOp RC:$rhs, RC:$lhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setugt RC:$lhs, RC:$rhs)), bb:$dst), (BNE (SLTuOp RC:$rhs, RC:$lhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setle RC:$lhs, RC:$rhs)), bb:$dst), (BEQ (SLTOp RC:$rhs, RC:$lhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setule RC:$lhs, RC:$rhs)), bb:$dst), (BEQ (SLTuOp RC:$rhs, RC:$lhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setge RC:$lhs, RC:$rhs)), bb:$dst), (BEQ (SLTOp RC:$lhs, RC:$rhs), ZERO, bb:$dst)>; def : Pat<(brcond (i32 (setuge RC:$lhs, RC:$rhs)), bb:$dst), (BEQ (SLTuOp RC:$lhs, RC:$rhs), ZERO, bb:$dst)>; def : Pat<(brcond RC:$cond, bb:$dst), (BNEOp RC:$cond, ZEROReg, bb:$dst)>; } defm : BrcondPatsSlt<GPR, BEQ, BNE, SLT, SLTU, SLTI, SLTIU, ZERO>;
符号付整数比較、符号なし整数比較について、==, !=, <, <=, >, >=
について定義している。これれらのパタンにより、比較演算および条件分岐が実装される。
./bin/llc -march=myriscvx32 -mcpu=simple32 -relocation-model=pic -filetype=asm ch8_1_1.bc -o -
_Z11test_ifctrlv: .frame $x8,8,$x1 .mask 0x00000000,0 .set noreorder .set nomacro # %bb.0: # %entry addi x2, x2, -8 addi x10, zero, 0 sw x10, 4(x2) lw x10, 4(x2) bne x10, zero, $BB0_2 j x10, $BB0_1 $BB0_1: # %if.then lw x10, 4(x2) addi x10, x10, 1 sw x10, 4(x2) j x10, $BB0_2 $BB0_2: # %if.end lw x10, 4(x2) addi x2, x2, 8 ret x1
正しく生成できていることが分かる。x10にロードした値をbne
命令によりゼロと比較し、ゼロでなければ関数から出るために$BB0_2
までジャンプする。そうでなければ$BB0_1
にジャンプし、1のインクリメントを行ってメモリへストア、そして$BB0_2
にジャンプする。命令として効率はさておき、正しい動きをしていることが分かる。