FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

オリジナルLLVM Backendを追加しよう (20. Conditional Moveの実装)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

jonathan2251.github.io

第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

どうにかうまく生成できたようだ。

f:id:msyksphinz:20190313021000p:plain