LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。
第8章の後半は、Conditional Moveの実装を行っていく。 Cpu0のConditional Moveはどのように指定実装しているのかと思ったら、シレっとCpu0にmovzとmovnという新規命令が追加されていた。 そういうことじゃないんだよ...
ここのオリジナル実装を付け加えるために、非常に苦労してしまった。
// Instantiation of instructions. def MOVZ_I_I : CondMovIntInt<CPURegs, CPURegs, 0x0a, "movz">; def MOVN_I_I : CondMovIntInt<CPURegs, CPURegs, 0x0b, "movn">;
このようなConditional Moveの命令はRISC-Vには存在しないので、どのように進めていくのかが問題になる。 オリジナルのRISC-Vの実装を参考にすると、SELECT_CCというOpcodeを使用して実装する。
def SDT_MYRISCVXSelectCC : SDTypeProfile<1, 5, [SDTCisSameAs<1, 2>, SDTCisSameAs<0, 4>, SDTCisSameAs<4, 5>]>; def SelectCC : SDNode<"MYRISCVXISD::SELECT_CC", SDT_MYRISCVXSelectCC, [SDNPInGlue]>;
これはオリジナルの実装のパクリなのだが、SDT_MYRISCVXSelectCC
は新しくConditional Move用のノードを作成する。
SDTypeProfile
の定義はあまり情報がないのだが、最初の引数(=1)がOutput Nodeの数、2番目の引数(=5)が入力ノードの数、そして最後に入力ノードの条件を書くらしい。
SDT_MYRISCVXSelectCC
を定義すると、SelectCC
というノードを定義する。これにより、MYRISCVXオリジナルの命令ノードであるMYRISCVXISD::SELECT_CC
というノードが作られる。
このMYRISCVXISD::SELECT_CC
に推論される条件を書き下していくわけだ。select IRと条件文を使用して、以下のように定義する。
let usesCustomInserter = 1 in class SelectCC_rrirr<RegisterClass RC, RegisterClass cmpty> : MYRISCVXPseudo<(outs RC:$dst), (ins cmpty:$lhs, cmpty:$rhs, simm12:$imm, RC:$truev, RC:$falsev), "", [(set RC:$dst, (SelectCC cmpty:$lhs, cmpty:$rhs, (i32 imm:$imm), RC:$truev, RC:$falsev))]>;
余談だがこのusesCustomInserter
がとても大切になる。これは、この変換後に作られたSelectCCのIRを、手動で変換する処理が入る、という印になる(という理解であっているかな?)。逆に言うと、このusesCustomInserter
を置いておかないと、後続の命令変換の最中にSELECT_CC
を変換することができないとしてエラーが出力されてしまう。
これでIRの定義ができたので、次にC++の実装に移る。通常のIRであるSELECT
は、直接RISC-Vの命令に変換できないので、上記で定義したSELECT_CC
に置き換える作業が必要となる。これが以下のSwitch文で定義される。
lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
SDValue MYRISCVXTargetLowering:: LowerOperation(SDValue Op, SelectionDAG &DAG) const { switch (Op.getOpcode()) { case ISD::GlobalAddress: return lowerGlobalAddress(Op, DAG); case ISD::SELECT: return lowerSELECT(Op, DAG); } return SDValue(); }
lowerSELECT
は以下のように定義した。これも要するに、SELECTの構文をSELECT_CCに置き換えている作業となっている。
SDValue MYRISCVXTargetLowering:: lowerSELECT(SDValue Op, SelectionDAG &DAG) const { SDValue CondV = Op.getOperand(0); SDValue TrueV = Op.getOperand(1); SDValue FalseV = Op.getOperand(2); SDLoc DL(Op); // (select condv, truev, falsev) // -> (myriscvxisd::select_cc condv, zero, setne, truev, falsev) SDValue Zero = DAG.getConstant(0, DL, MVT::i32); SDValue SetNE = DAG.getConstant(ISD::SETNE, DL, MVT::i32); SDVTList VTs = DAG.getVTList(Op.getValueType(), MVT::Glue); SDValue Ops[] = {CondV, Zero, SetNE, TrueV, FalseV}; return DAG.getNode(MYRISCVXISD::SELECT_CC, DL, VTs, Ops); }
こうしてSELECT_CCの構文は、最後にRISC-Vの比較命令と分岐命令に置き換えていく。これがその実装だ。 (これもオリジナルのRISC-Vのものを丸パクリした)
lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
MachineBasicBlock * MYRISCVXTargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI, MachineBasicBlock *BB) const { dbgs() << "MYRISCVXTargetLowering::EmitInstrWithCustomInserter\n"; switch (MI.getOpcode()) { default: llvm_unreachable("Unexpected instr type to insert"); case MYRISCVX::Select_GPR_Using_CC_GPR: break; } ... // Insert appropriate branch. unsigned LHS = MI.getOperand(1).getReg(); unsigned RHS = MI.getOperand(2).getReg(); auto CC = static_cast<ISD::CondCode>(MI.getOperand(3).getImm()); unsigned Opcode = getBranchOpcodeForIntCondCode(CC); BuildMI(HeadMBB, DL, TII.get(Opcode)) .addReg(LHS) .addReg(RHS) .addMBB(TailMBB); // IfFalseMBB just falls through to TailMBB. IfFalseMBB->addSuccessor(TailMBB); // %Result = phi [ %TrueValue, HeadMBB ], [ %FalseValue, IfFalseMBB ] BuildMI(*TailMBB, TailMBB->begin(), DL, TII.get(MYRISCVX::PHI), MI.getOperand(0).getReg()) .addReg(MI.getOperand(4).getReg()) .addMBB(HeadMBB) .addReg(MI.getOperand(5).getReg()) .addMBB(IfFalseMBB); MI.eraseFromParent(); // The pseudo instruction is gone now. return TailMBB;
これで命令を生成してみる。テストにはch8_2_select.cpp
を使用している。
./bin/clang -c -target mips ../lbdex/input/ch8_2_select.cpp -emit-llvm ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm ch8_2_select.bc -o -
# %bb.0: # %entry addi x2, x2, -8 addi x10, x0, 1 sw x10, 4(x2) sw x0, 0(x2) lw x11, 4(x2) addi x12, x0, 0 xor x11, x11, x12 sltu x13, x0, x11 addi x11, x0, 3 bne x13, x12, $BB1_2 # %bb.1: # %entry addi x10, x11, 0 $BB1_2: # %entry
どうにかうまく生成できたようだ。