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
どうにかうまく生成できたようだ。
