前回の続き。
Legalizeでは、ターゲット固有のノードへの変換を行う。LLVM IRでは様々な演算ノードを定義しているが、ターゲットアーキテクチャによってはサポートしていない演算もある。
このような演算に関しては、ターゲットが生成できるノードに変換するなどの指示を出す必要がある。
これらについては、後述するMYRSCVXTargetLowering
で指示を出すが、例えば、ローテート命令はRISC-Vではそのまま生成できない。したがって、以下のように記述することでローテート演算の生成を抑制する。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
setOperationAction(ISD::ROTL, XLenVT, Expand); setOperationAction(ISD::ROTR, XLenVT, Expand);
setOperationAction
により、当該ノードの動作を決定する。上記の例だと、ISD::ROTL
演算の`XLenVT‘サイズの演算について、Expandのアクションを設定する。
llvm-myriscvx80/include/llvm/CodeGen/TargetLowering.h
/// Indicate that the specified operation does not work with the specified /// type and indicate what to do about it. Note that VT may refer to either /// the type of a result or that of an operand of Op. void setOperationAction(unsigned Op, MVT VT, LegalizeAction Action) { assert(Op < array_lengthof(OpActions[0]) && "Table isn't big enough!"); OpActions[(unsigned)VT.SimpleTy][Op] = Action; }
上記の場合だとノードのアクションがExpandに設定されるのだが、Legalizeの実行時にSelectionDAGLegalize::LegalizeOp
が呼ばれ判断が行われる。
Expandの場合は、ExpandNode()
が呼び出され、さらにExpandROT
により、より単純なノードへの置き換えが行われる。
llvm-myriscvx80/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
/// Return a legal replacement for the given operation, with all legal operands. void SelectionDAGLegalize::LegalizeOp(SDNode *Node) { LLVM_DEBUG(dbgs() << "\nLegalizing: "; Node->dump(&DAG)); ... switch (Action) { case TargetLowering::Legal: LLVM_DEBUG(dbgs() << "Legal node: nothing to do\n"); ... case TargetLowering::Expand: if (ExpandNode(Node)) return; LLVM_FALLTHROUGH; ...
llvm-myriscvx80/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
bool SelectionDAGLegalize::ExpandNode(SDNode *Node) { LLVM_DEBUG(dbgs() << "Trying to expand node\n"); SmallVector<SDValue, 8> Results; ... case ISD::ROTL: case ISD::ROTR: if (TLI.expandROT(Node, Tmp1, DAG)) Results.push_back(Tmp1); break; ...
最終的に、シフト演算と論理演算命令に置き換わる。
llvm-myriscvx80/lib/CodeGen/SelectionDAG/TargetLowering.cpp
// TODO: Merge with expandFunnelShift. bool TargetLowering::expandROT(SDNode *Node, SDValue &Result, SelectionDAG &DAG) const { EVT VT = Node->getValueType(0); unsigned EltSizeInBits = VT.getScalarSizeInBits(); ... // Otherwise, // (rotl x, c) -> (or (shl x, (and c, w-1)), (srl x, (and w-c, w-1))) // (rotr x, c) -> (or (srl x, (and c, w-1)), (shl x, (and w-c, w-1))) // assert(isPowerOf2_32(EltSizeInBits) && EltSizeInBits > 1 && "Expecting the type bitwidth to be a power of 2"); unsigned ShOpc = IsLeft ? ISD::SHL : ISD::SRL; unsigned HsOpc = IsLeft ? ISD::SRL : ISD::SHL; SDValue BitWidthMinusOneC = DAG.getConstant(EltSizeInBits - 1, DL, ShVT); SDValue NegOp1 = DAG.getNode(ISD::SUB, DL, ShVT, BitWidthC, Op1); SDValue And0 = DAG.getNode(ISD::AND, DL, ShVT, Op1, BitWidthMinusOneC); SDValue And1 = DAG.getNode(ISD::AND, DL, ShVT, NegOp1, BitWidthMinusOneC); Result = DAG.getNode(ISD::OR, DL, VT, DAG.getNode(ShOpc, DL, VT, Op0, And0), DAG.getNode(HsOpc, DL, VT, Op0, And1)); return true; }
命令のSelection
ここまで来るとほぼ一対一でSelectionDAGのノードを命令に変換することができる。
次は、SelectionDAGのノードを命令に相当するノードMI(Machine Instruction)に変換する。
これには、DAGToDAGISel
によって処理される。
今回作成するMYRISCVXアーキテクチャではMYRISCVXDAGToDAGISel
によって行われる。
MYRISCVXDAGToDAGISel::Select()
によって行われる。
//@Select { /// Select instructions not customized! Used for /// expanded, promoted and normal instructions void MYRISCVXDAGToDAGISel::Select(SDNode *Node) { //@Select } ... // Select the default instruction SelectCode(Node); }
SelectCode
はMYRISCVXGenDAGISel.inc
に定義されており、TableGenにより生成されたノードから命令ノードへの変換が行われる。
build-myriscvx80/lib/Target/MYRISCVX/MYRISCVXGenDAGISel.inc
#if defined(GET_DAGISEL_BODY) || DAGISEL_INLINE void DAGISEL_CLASS_COLONCOLON SelectCode(SDNode *N) { // Some target values are emitted as 2 bytes, TARGET_VAL handles ...
これにより、以下のようなDAGが生成される。MYRISCVXISD::Ret
がRetRA
に変換されている。