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は同じ型でなければならない。 |
SELECTとSELECT_CC、両方対応しても良いのだが、SELECT_CCに対応しておき、SELECTはSELECT_CCに変換する、という作戦を取ろうと思う。以下のようにSELECTノードはSELECT_CCに変換されるようにする。
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とZeroをSETNEで比較し、比較結果によってTrueVとFalseVのどちらかを選択するというSELECT_CCを組み立てた。SELECTはMYRISCVXISD::SELECT_CCに置き換えられ、最終的に命令に変換されることになる。