| //===-- CSKYISelDAGToDAG.cpp - A dag to dag inst selector for CSKY---------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file defines an instruction selector for the CSKY target. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "CSKY.h" | |
| #include "CSKYSubtarget.h" | |
| #include "CSKYTargetMachine.h" | |
| #include "MCTargetDesc/CSKYMCTargetDesc.h" | |
| #include "llvm/CodeGen/MachineFrameInfo.h" | |
| #include "llvm/CodeGen/SelectionDAG.h" | |
| #include "llvm/CodeGen/SelectionDAGISel.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "csky-isel" | |
| #define PASS_NAME "CSKY DAG->DAG Pattern Instruction Selection" | |
| namespace { | |
| class CSKYDAGToDAGISel : public SelectionDAGISel { | |
| const CSKYSubtarget *Subtarget; | |
| public: | |
| static char ID; | |
| explicit CSKYDAGToDAGISel(CSKYTargetMachine &TM, CodeGenOpt::Level OptLevel) | |
| : SelectionDAGISel(ID, TM, OptLevel) {} | |
| bool runOnMachineFunction(MachineFunction &MF) override { | |
| // Reset the subtarget each time through. | |
| Subtarget = &MF.getSubtarget<CSKYSubtarget>(); | |
| SelectionDAGISel::runOnMachineFunction(MF); | |
| return true; | |
| } | |
| void Select(SDNode *N) override; | |
| bool selectAddCarry(SDNode *N); | |
| bool selectSubCarry(SDNode *N); | |
| bool selectBITCAST_TO_LOHI(SDNode *N); | |
| bool selectInlineAsm(SDNode *N); | |
| SDNode *createGPRPairNode(EVT VT, SDValue V0, SDValue V1); | |
| bool SelectInlineAsmMemoryOperand(const SDValue &Op, unsigned ConstraintID, | |
| std::vector<SDValue> &OutOps) override; | |
| #include "CSKYGenDAGISel.inc" | |
| }; | |
| } // namespace | |
| char CSKYDAGToDAGISel::ID = 0; | |
| INITIALIZE_PASS(CSKYDAGToDAGISel, DEBUG_TYPE, PASS_NAME, false, false) | |
| void CSKYDAGToDAGISel::Select(SDNode *N) { | |
| // If we have a custom node, we have already selected | |
| if (N->isMachineOpcode()) { | |
| LLVM_DEBUG(dbgs() << "== "; N->dump(CurDAG); dbgs() << "\n"); | |
| N->setNodeId(-1); | |
| return; | |
| } | |
| SDLoc Dl(N); | |
| unsigned Opcode = N->getOpcode(); | |
| bool IsSelected = false; | |
| switch (Opcode) { | |
| default: | |
| break; | |
| case ISD::ADDCARRY: | |
| IsSelected = selectAddCarry(N); | |
| break; | |
| case ISD::SUBCARRY: | |
| IsSelected = selectSubCarry(N); | |
| break; | |
| case ISD::GLOBAL_OFFSET_TABLE: { | |
| Register GP = Subtarget->getInstrInfo()->getGlobalBaseReg(*MF); | |
| ReplaceNode(N, CurDAG->getRegister(GP, N->getValueType(0)).getNode()); | |
| IsSelected = true; | |
| break; | |
| } | |
| case ISD::FrameIndex: { | |
| SDValue Imm = CurDAG->getTargetConstant(0, Dl, MVT::i32); | |
| int FI = cast<FrameIndexSDNode>(N)->getIndex(); | |
| SDValue TFI = CurDAG->getTargetFrameIndex(FI, MVT::i32); | |
| ReplaceNode(N, CurDAG->getMachineNode(Subtarget->hasE2() ? CSKY::ADDI32 | |
| : CSKY::ADDI16XZ, | |
| Dl, MVT::i32, TFI, Imm)); | |
| IsSelected = true; | |
| break; | |
| } | |
| case CSKYISD::BITCAST_TO_LOHI: | |
| IsSelected = selectBITCAST_TO_LOHI(N); | |
| break; | |
| case ISD::INLINEASM: | |
| case ISD::INLINEASM_BR: | |
| IsSelected = selectInlineAsm(N); | |
| break; | |
| } | |
| if (IsSelected) | |
| return; | |
| // Select the default instruction. | |
| SelectCode(N); | |
| } | |
| bool CSKYDAGToDAGISel::selectInlineAsm(SDNode *N) { | |
| std::vector<SDValue> AsmNodeOperands; | |
| unsigned Flag, Kind; | |
| bool Changed = false; | |
| unsigned NumOps = N->getNumOperands(); | |
| // Normally, i64 data is bounded to two arbitrary GRPs for "%r" constraint. | |
| // However, some instructions (e.g. mula.s32) require GPR pair. | |
| // Since there is no constraint to explicitly specify a | |
| // reg pair, we use GPRPair reg class for "%r" for 64-bit data. | |
| SDLoc dl(N); | |
| SDValue Glue = | |
| N->getGluedNode() ? N->getOperand(NumOps - 1) : SDValue(nullptr, 0); | |
| SmallVector<bool, 8> OpChanged; | |
| // Glue node will be appended late. | |
| for (unsigned i = 0, e = N->getGluedNode() ? NumOps - 1 : NumOps; i < e; | |
| ++i) { | |
| SDValue op = N->getOperand(i); | |
| AsmNodeOperands.push_back(op); | |
| if (i < InlineAsm::Op_FirstOperand) | |
| continue; | |
| if (ConstantSDNode *C = dyn_cast<ConstantSDNode>(N->getOperand(i))) { | |
| Flag = C->getZExtValue(); | |
| Kind = InlineAsm::getKind(Flag); | |
| } else | |
| continue; | |
| // Immediate operands to inline asm in the SelectionDAG are modeled with | |
| // two operands. The first is a constant of value InlineAsm::Kind_Imm, and | |
| // the second is a constant with the value of the immediate. If we get here | |
| // and we have a Kind_Imm, skip the next operand, and continue. | |
| if (Kind == InlineAsm::Kind_Imm) { | |
| SDValue op = N->getOperand(++i); | |
| AsmNodeOperands.push_back(op); | |
| continue; | |
| } | |
| unsigned NumRegs = InlineAsm::getNumOperandRegisters(Flag); | |
| if (NumRegs) | |
| OpChanged.push_back(false); | |
| unsigned DefIdx = 0; | |
| bool IsTiedToChangedOp = false; | |
| // If it's a use that is tied with a previous def, it has no | |
| // reg class constraint. | |
| if (Changed && InlineAsm::isUseOperandTiedToDef(Flag, DefIdx)) | |
| IsTiedToChangedOp = OpChanged[DefIdx]; | |
| // Memory operands to inline asm in the SelectionDAG are modeled with two | |
| // operands: a constant of value InlineAsm::Kind_Mem followed by the input | |
| // operand. If we get here and we have a Kind_Mem, skip the next operand (so | |
| // it doesn't get misinterpreted), and continue. We do this here because | |
| // it's important to update the OpChanged array correctly before moving on. | |
| if (Kind == InlineAsm::Kind_Mem) { | |
| SDValue op = N->getOperand(++i); | |
| AsmNodeOperands.push_back(op); | |
| continue; | |
| } | |
| if (Kind != InlineAsm::Kind_RegUse && Kind != InlineAsm::Kind_RegDef && | |
| Kind != InlineAsm::Kind_RegDefEarlyClobber) | |
| continue; | |
| unsigned RC; | |
| bool HasRC = InlineAsm::hasRegClassConstraint(Flag, RC); | |
| if ((!IsTiedToChangedOp && (!HasRC || RC != CSKY::GPRRegClassID)) || | |
| NumRegs != 2) | |
| continue; | |
| assert((i + 2 < NumOps) && "Invalid number of operands in inline asm"); | |
| SDValue V0 = N->getOperand(i + 1); | |
| SDValue V1 = N->getOperand(i + 2); | |
| unsigned Reg0 = cast<RegisterSDNode>(V0)->getReg(); | |
| unsigned Reg1 = cast<RegisterSDNode>(V1)->getReg(); | |
| SDValue PairedReg; | |
| MachineRegisterInfo &MRI = MF->getRegInfo(); | |
| if (Kind == InlineAsm::Kind_RegDef || | |
| Kind == InlineAsm::Kind_RegDefEarlyClobber) { | |
| // Replace the two GPRs with 1 GPRPair and copy values from GPRPair to | |
| // the original GPRs. | |
| Register GPVR = MRI.createVirtualRegister(&CSKY::GPRPairRegClass); | |
| PairedReg = CurDAG->getRegister(GPVR, MVT::i64); | |
| SDValue Chain = SDValue(N, 0); | |
| SDNode *GU = N->getGluedUser(); | |
| SDValue RegCopy = | |
| CurDAG->getCopyFromReg(Chain, dl, GPVR, MVT::i64, Chain.getValue(1)); | |
| // Extract values from a GPRPair reg and copy to the original GPR reg. | |
| SDValue Sub0 = | |
| CurDAG->getTargetExtractSubreg(CSKY::sub32_0, dl, MVT::i32, RegCopy); | |
| SDValue Sub1 = | |
| CurDAG->getTargetExtractSubreg(CSKY::sub32_32, dl, MVT::i32, RegCopy); | |
| SDValue T0 = | |
| CurDAG->getCopyToReg(Sub0, dl, Reg0, Sub0, RegCopy.getValue(1)); | |
| SDValue T1 = CurDAG->getCopyToReg(Sub1, dl, Reg1, Sub1, T0.getValue(1)); | |
| // Update the original glue user. | |
| std::vector<SDValue> Ops(GU->op_begin(), GU->op_end() - 1); | |
| Ops.push_back(T1.getValue(1)); | |
| CurDAG->UpdateNodeOperands(GU, Ops); | |
| } else { | |
| // For Kind == InlineAsm::Kind_RegUse, we first copy two GPRs into a | |
| // GPRPair and then pass the GPRPair to the inline asm. | |
| SDValue Chain = AsmNodeOperands[InlineAsm::Op_InputChain]; | |
| // As REG_SEQ doesn't take RegisterSDNode, we copy them first. | |
| SDValue T0 = | |
| CurDAG->getCopyFromReg(Chain, dl, Reg0, MVT::i32, Chain.getValue(1)); | |
| SDValue T1 = | |
| CurDAG->getCopyFromReg(Chain, dl, Reg1, MVT::i32, T0.getValue(1)); | |
| SDValue Pair = SDValue(createGPRPairNode(MVT::i64, T0, T1), 0); | |
| // Copy REG_SEQ into a GPRPair-typed VR and replace the original two | |
| // i32 VRs of inline asm with it. | |
| Register GPVR = MRI.createVirtualRegister(&CSKY::GPRPairRegClass); | |
| PairedReg = CurDAG->getRegister(GPVR, MVT::i64); | |
| Chain = CurDAG->getCopyToReg(T1, dl, GPVR, Pair, T1.getValue(1)); | |
| AsmNodeOperands[InlineAsm::Op_InputChain] = Chain; | |
| Glue = Chain.getValue(1); | |
| } | |
| Changed = true; | |
| if (PairedReg.getNode()) { | |
| OpChanged[OpChanged.size() - 1] = true; | |
| Flag = InlineAsm::getFlagWord(Kind, 1 /* RegNum*/); | |
| if (IsTiedToChangedOp) | |
| Flag = InlineAsm::getFlagWordForMatchingOp(Flag, DefIdx); | |
| else | |
| Flag = InlineAsm::getFlagWordForRegClass(Flag, CSKY::GPRPairRegClassID); | |
| // Replace the current flag. | |
| AsmNodeOperands[AsmNodeOperands.size() - 1] = | |
| CurDAG->getTargetConstant(Flag, dl, MVT::i32); | |
| // Add the new register node and skip the original two GPRs. | |
| AsmNodeOperands.push_back(PairedReg); | |
| // Skip the next two GPRs. | |
| i += 2; | |
| } | |
| } | |
| if (Glue.getNode()) | |
| AsmNodeOperands.push_back(Glue); | |
| if (!Changed) | |
| return false; | |
| SDValue New = CurDAG->getNode(N->getOpcode(), SDLoc(N), | |
| CurDAG->getVTList(MVT::Other, MVT::Glue), | |
| AsmNodeOperands); | |
| New->setNodeId(-1); | |
| ReplaceNode(N, New.getNode()); | |
| return true; | |
| } | |
| bool CSKYDAGToDAGISel::selectBITCAST_TO_LOHI(SDNode *N) { | |
| SDLoc Dl(N); | |
| auto VT = N->getValueType(0); | |
| auto V = N->getOperand(0); | |
| if (!Subtarget->hasFPUv2DoubleFloat()) | |
| return false; | |
| SDValue V1 = SDValue(CurDAG->getMachineNode(CSKY::FMFVRL_D, Dl, VT, V), 0); | |
| SDValue V2 = SDValue(CurDAG->getMachineNode(CSKY::FMFVRH_D, Dl, VT, V), 0); | |
| ReplaceUses(SDValue(N, 0), V1); | |
| ReplaceUses(SDValue(N, 1), V2); | |
| CurDAG->RemoveDeadNode(N); | |
| return true; | |
| } | |
| bool CSKYDAGToDAGISel::selectAddCarry(SDNode *N) { | |
| MachineSDNode *NewNode = nullptr; | |
| auto Type0 = N->getValueType(0); | |
| auto Type1 = N->getValueType(1); | |
| auto Op0 = N->getOperand(0); | |
| auto Op1 = N->getOperand(1); | |
| auto Op2 = N->getOperand(2); | |
| SDLoc Dl(N); | |
| if (isNullConstant(Op2)) { | |
| auto *CA = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::CLRC32 : CSKY::CLRC16, Dl, Type1); | |
| NewNode = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::ADDC32 : CSKY::ADDC16, Dl, {Type0, Type1}, | |
| {Op0, Op1, SDValue(CA, 0)}); | |
| } else if (isOneConstant(Op2)) { | |
| auto *CA = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::SETC32 : CSKY::SETC16, Dl, Type1); | |
| NewNode = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::ADDC32 : CSKY::ADDC16, Dl, {Type0, Type1}, | |
| {Op0, Op1, SDValue(CA, 0)}); | |
| } else { | |
| NewNode = CurDAG->getMachineNode(Subtarget->has2E3() ? CSKY::ADDC32 | |
| : CSKY::ADDC16, | |
| Dl, {Type0, Type1}, {Op0, Op1, Op2}); | |
| } | |
| ReplaceNode(N, NewNode); | |
| return true; | |
| } | |
| static SDValue InvertCarryFlag(const CSKYSubtarget *Subtarget, | |
| SelectionDAG *DAG, SDLoc Dl, SDValue OldCarry) { | |
| auto NewCarryReg = | |
| DAG->getMachineNode(Subtarget->has2E3() ? CSKY::MVCV32 : CSKY::MVCV16, Dl, | |
| MVT::i32, OldCarry); | |
| auto NewCarry = | |
| DAG->getMachineNode(Subtarget->hasE2() ? CSKY::BTSTI32 : CSKY::BTSTI16, | |
| Dl, OldCarry.getValueType(), SDValue(NewCarryReg, 0), | |
| DAG->getTargetConstant(0, Dl, MVT::i32)); | |
| return SDValue(NewCarry, 0); | |
| } | |
| bool CSKYDAGToDAGISel::selectSubCarry(SDNode *N) { | |
| MachineSDNode *NewNode = nullptr; | |
| auto Type0 = N->getValueType(0); | |
| auto Type1 = N->getValueType(1); | |
| auto Op0 = N->getOperand(0); | |
| auto Op1 = N->getOperand(1); | |
| auto Op2 = N->getOperand(2); | |
| SDLoc Dl(N); | |
| if (isNullConstant(Op2)) { | |
| auto *CA = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::SETC32 : CSKY::SETC16, Dl, Type1); | |
| NewNode = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::SUBC32 : CSKY::SUBC16, Dl, {Type0, Type1}, | |
| {Op0, Op1, SDValue(CA, 0)}); | |
| } else if (isOneConstant(Op2)) { | |
| auto *CA = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::CLRC32 : CSKY::CLRC16, Dl, Type1); | |
| NewNode = CurDAG->getMachineNode( | |
| Subtarget->has2E3() ? CSKY::SUBC32 : CSKY::SUBC16, Dl, {Type0, Type1}, | |
| {Op0, Op1, SDValue(CA, 0)}); | |
| } else { | |
| auto CarryIn = InvertCarryFlag(Subtarget, CurDAG, Dl, Op2); | |
| NewNode = CurDAG->getMachineNode(Subtarget->has2E3() ? CSKY::SUBC32 | |
| : CSKY::SUBC16, | |
| Dl, {Type0, Type1}, {Op0, Op1, CarryIn}); | |
| } | |
| auto CarryOut = InvertCarryFlag(Subtarget, CurDAG, Dl, SDValue(NewNode, 1)); | |
| ReplaceUses(SDValue(N, 0), SDValue(NewNode, 0)); | |
| ReplaceUses(SDValue(N, 1), CarryOut); | |
| CurDAG->RemoveDeadNode(N); | |
| return true; | |
| } | |
| SDNode *CSKYDAGToDAGISel::createGPRPairNode(EVT VT, SDValue V0, SDValue V1) { | |
| SDLoc dl(V0.getNode()); | |
| SDValue RegClass = | |
| CurDAG->getTargetConstant(CSKY::GPRPairRegClassID, dl, MVT::i32); | |
| SDValue SubReg0 = CurDAG->getTargetConstant(CSKY::sub32_0, dl, MVT::i32); | |
| SDValue SubReg1 = CurDAG->getTargetConstant(CSKY::sub32_32, dl, MVT::i32); | |
| const SDValue Ops[] = {RegClass, V0, SubReg0, V1, SubReg1}; | |
| return CurDAG->getMachineNode(TargetOpcode::REG_SEQUENCE, dl, VT, Ops); | |
| } | |
| bool CSKYDAGToDAGISel::SelectInlineAsmMemoryOperand( | |
| const SDValue &Op, unsigned ConstraintID, std::vector<SDValue> &OutOps) { | |
| switch (ConstraintID) { | |
| case InlineAsm::Constraint_m: | |
| // We just support simple memory operands that have a single address | |
| // operand and need no special handling. | |
| OutOps.push_back(Op); | |
| return false; | |
| default: | |
| break; | |
| } | |
| return true; | |
| } | |
| FunctionPass *llvm::createCSKYISelDag(CSKYTargetMachine &TM, | |
| CodeGenOpt::Level OptLevel) { | |
| return new CSKYDAGToDAGISel(TM, OptLevel); | |
| } |