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
に置き換えられ、最終的に命令に変換されることになる。