SELECT
ノードは以下の実装を用いてMYRISCVXISD::SELECT_CC
に変換する。SELECT
ノードはカスタム実装に置き換え、(MYRISCVXISD
ではないデフォルトの)SELECT_CC
はCombine時に生成しないようにする。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
MYRISCVXTargetLowering::MYRISCVXTargetLowering(const MYRISCVXTargetMachine &TM, const MYRISCVXSubtarget &STI) ... // SELECTノードはカスタム実装に置き換え setOperationAction(ISD::SELECT, MVT::i32, Custom); // MYRISCVXISDではないデフォルトのSELECT_CCはCombine時に生成しない setOperationAction(ISD::SELECT_CC, MVT::i32, Expand);
そして、MYRISCVXTargetLowering::lowerOperation()
でSELECT
ノードの処理をMYRISCVXTargetLowering::lowerSELECT()
で処理するように仕向ける。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
SDValue MYRISCVXTargetLowering:: LowerOperation(SDValue Op, SelectionDAG &DAG) const { switch (Op.getOpcode()) { case ISD::SELECT : return lowerSELECT(Op, DAG); ...
MYRISCVXTargetLowering::lowerSELECT()
の実装を覗いてみる。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
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
ノードをMYRISCVXISD::SELECT_CC
に置き換えるためのノードの組み立てを行っている。比較対象のオペランドOpとZero
をSETNE
で比較し、比較結果によってTrueV
とFalseV
のどちらかを選択するというSELECT_CC
を組み立てた。SELECT
はMYRISCVXISD::SELECT_CC
に置き換えられ、最終的に命令に変換されることになる。
MYRISCVXISD::SELECT_CC
ノードから命令を組み立てる
MYRISCVXISD::SELECT_CC
ノードは以下のように定義されている。MYRISCVXSelectCC
というノードを作り、その制約条件をSDT_MYRISCVXSelectCC
に記述する。この制約はISD::SelectCC
と同様だ。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
# MYRISCVX::SELECT_CCの条件。出力ノードは1つ、入力ノードは5つ。 def SDT_MYRISCVXSelectCC : SDTypeProfile<1, 5, [SDTCisSameAs<1, 2>, SDTCisSameAs<0, 4>, SDTCisSameAs<4, 5>]>; # MYRISCVX::SELECT_CCを定義する。SDT_MYRISCVXSelectCCのノード制約を使用する。 def MYRISCVXSelectCC : SDNode<"MYRISCVXISD::SELECT_CC", SDT_MYRISCVXSelectCC, [SDNPInGlue]>;
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, (MYRISCVXSelectCC cmpty:$lhs, cmpty:$rhs, (i32 imm:$imm), RC:$truev, RC:$falsev))]>; def Select_GPR_Using_CC_GPR : SelectCC_rrirr<GPR, GPR>;
このMYRISCVXSelectCC
を用いてパタンを作成する。比較対象のcmpty:$lhs, cmpty:$rhs
に対して比較結果がTrueの場合にはRC:$truev
が、不成立の場合にはRC:$falsev
が選択され出力RC:$dst
が返されるようにしている。ただし、このままではどのような比較演算が実行されるのかが分からない。これは、実際にCコード側で組み立てていくことになる。
このとき、このusesCustomInserter
がとても大切になる。これは、この変換後に作られたMYRISCVXSelectCC
のIRを、手動で変換する処理が入る、という印になる。逆に言うと、このusesCustomInserter
を置いておかないと、後続の命令変換の最中にSELECT_CC
を変換することができないとしてエラーが出力されてしまうのだ。
以下に、MYRISCVXISD::SELECT_CC
を具体的な命令に置き換える実装を示す。
3項演算子を実現するので、以下のようなフローになる。
- 比較演算が成立した場合の値を、Destination Registerに格納しておく。
- 比較演算を実行する。
- 比較結果がFalseであれば、Destination RegisterにFalse側の値を格納する。
このため、大きく分けて以下のマシンコードブロックを作成する。
HeadMBB
: 先頭のブロック(与えられたMachineBasicBlock *BB
に相当)TailMBB
: 3項演算子ブロックの末尾。TrueでもFalseでも最終的にここに飛んでくる。IfFalseMBB
: 比較結果がFalseである場合飛んでくる。False時の値を設定する。llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
MachineBasicBlock * MYRISCVXTargetLowering::EmitInstrWithCustomInserter(MachineInstr &MI, MachineBasicBlock *BB) const { const TargetInstrInfo &TII = *BB->getParent()->getSubtarget().getInstrInfo(); const BasicBlock *LLVM_BB = BB->getBasicBlock(); DebugLoc DL = MI.getDebugLoc(); MachineFunction::iterator I = ++BB->getIterator(); MachineBasicBlock *HeadMBB = BB; MachineFunction *F = BB->getParent(); MachineBasicBlock *TailMBB = F->CreateMachineBasicBlock(LLVM_BB); MachineBasicBlock *IfFalseMBB = F->CreateMachineBasicBlock(LLVM_BB); F->insert(I, IfFalseMBB); F->insert(I, TailMBB); // Move all remaining instructions to TailMBB. TailMBB->splice(TailMBB->begin(), HeadMBB, std::next(MachineBasicBlock::iterator(MI)), HeadMBB->end()); // Update machine-CFG edges by transferring all successors of the current // block to the new block which will contain the Phi node for the select. TailMBB->transferSuccessorsAndUpdatePHIs(HeadMBB); // Set the successors for HeadMBB. HeadMBB->addSuccessor(IfFalseMBB); HeadMBB->addSuccessor(TailMBB); // 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; }
EmitInstrWithCustomInserter()
の中で比較とその結果に基づく選択の命令を抽出する。まず左右の条件どうしを比較する命令を作成する。
BuildMI(HeadMBB, DL, TII.get(Opcode)) .addReg(LHS) .addReg(RHS) .addMBB(TailMBB);
OpCode
はgetBranchOpcodeForIntCondCode()
で選択する。比較演算によって条件分岐命令を選択しているが、つまりBEQ LHS, RHS, TailMBB
という命令を組み立て、LHSとRHSの比較を行っている。
次に、PHIノードを組み立てる。PHIノードはざっくりと説明すると、「飛んできたブロックによって選択する値を選ぶ」というノードだ。上記の比較命令により、LHSとRHSの比較結果がTrueである場合TailMBB
に飛ぶ訳だが、そうでない場合は、HeadMBB
からIfFalseMBB
に飛ぶわけだので、
HeadMBB->addSuccessor(IfFalseMBB);
IfFalseMBB
から来た場合はオペランド5(False時の値)、そうでなくHeadMBB
から来た場合はオペランド4(True時の値)を選択するようにPHIノードを構築している。これは最終的に分岐命令に分解され、命令が出力される。
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);
再ビルドを行い、上記のサンプルプログラムを動かした時の出力結果は以下のようになる。
# %bb.0: # %entry addi x2, x2, -8 addi x10, zero, 1 sb x10, 4(x2) addi x12, zero, 0 sw x12, 0(x2) lbu x10, 4(x2) andi x13, x10, 1 addi x11, zero, 20 addi x10, zero, 10 bne x13, x12, $BB0_2 # %bb.1: # %entry addi x10, x11, 0 $BB0_2: # %entry sw x10, 0(x2) lw x10, 0(x2) addi x2, x2, 8 ret x1