FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (27. 3項演算子のためのSELECTノードの処理)

f:id:msyksphinz:20190425001356p:plain

3項演算子コンパイルしてみると、以下のようなLLVM IRが生成されることが分かる。

int test_movx_1()
{
  volatile int a = 1;
  int c = 0;

  c = !a ? 1:3;

  return c;
}
./bin/clang --target=mips-unknown-elf ../lbdex/input/ch8_2_select.cpp -c -emit-llvm
./bin/llvm-dis ch8_2_select.bc -o -
...
  %lnot = xor i1 %tobool, true
  %1 = zext i1 %lnot to i64
  %cond = select i1 %lnot, i32 1, i32 3
...

SELECTノードが生成されている。SELECTノードはそのまま3項演算子で、これをそのままRISC-Vの命令に変換できればいいのだが、RISC-VにはConditional Moveのような命令が存在しないので別の命令に変換しなければならない。

SEELCT SELECT (COND, TRUEVAL, FALSEVAL).
3項演算子を構築する。
引数1. 1ビットの整数
引数2. 引数1がTrueの場合に選択される値
引数3. 引数1がFalseの場合に選択される値
選択される値(引数0)と、TRUEVAL、FALSEVALはすべて同じ型でなければならない。

3項演算子を実現するためには単純に命令の置き換えで実現できないので、分岐命令を活用する必要がありる。そのためにはラベルを作成したり、複雑な処理が必要なので、ノードの生成はC++で実装することにする。

もう一つ、SELECTノードに似ているノードとしてSELECT_CCというノードが存在する。SELECT_CCはより複雑で、比較対象のオペランドの指定、比較命令、True時、False時の選択するオペランドを設定できる。

SELECT_CC SELECT_CC (LHS, RHS, TRUEVAL, FALSEVAL, OP)
3項演算子だ。LHSとRHSをOPで比較し、比較結果がTrueならばTRUEVALを返し、比較結果がFALSEならばFALSEVALを返する。
引数1. 比較オペランド1
引数2. 比較オペランド2
引数3. 比較結果がTrueである場合の選択される値
比較4. 比較結果がFalseである場合の選択される値
引数5. 比較演算
選択される値(引数0)と、TRUEVAL, FALSEVALはすべて同じ型でなければならない。
比較オペランド1と比較オペランド2は同じ型でなければならない。

SELECTSELECT_CC、両方対応しても良いのだが、SELECT_CCに対応しておき、SELECTSELECT_CCに変換する、という作戦を取ろうと思う。以下のようにSELECTノードはSELECT_CCに変換されるようにする。

f:id:msyksphinz:20190622115207p:plain
Successfully custom legalized node
 ... replacing: t13: i32 = select t23, Constant:i32<10>, Constant:i32<20>
     with:      t26: i32,glue = MYRISCVXISD::SELECT_CC t23, Constant:i32<0>, Constant:i32<22>, Constant:i32<10>, Constant:i32<20>

SELECT(t23, 10, 20)というノードが、SELECT_CC (t23, 0, OP, 10, 20)に変換されていることが分かる。

SELECTノードをMYRISCVXISD::SELECT_CCに変換する

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とZeroSETNEで比較し、比較結果によってTrueVFalseVのどちらかを選択するというSELECT_CCを組み立てた。SELECTMYRISCVXISD::SELECT_CCに置き換えられ、最終的に命令に変換されることになる。