| //===-- VECustomDAG.h - VE Custom DAG Nodes ------------*- C++ -*-===// |
| // |
| // 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 the interfaces that VE uses to lower LLVM code into a |
| // selection DAG. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "VECustomDAG.h" |
| |
| #ifndef DEBUG_TYPE |
| #define DEBUG_TYPE "vecustomdag" |
| #endif |
| |
| namespace llvm { |
| |
| bool isPackedVectorType(EVT SomeVT) { |
| if (!SomeVT.isVector()) |
| return false; |
| return SomeVT.getVectorNumElements() > StandardVectorWidth; |
| } |
| |
| MVT splitVectorType(MVT VT) { |
| if (!VT.isVector()) |
| return VT; |
| return MVT::getVectorVT(VT.getVectorElementType(), StandardVectorWidth); |
| } |
| |
| MVT getLegalVectorType(Packing P, MVT ElemVT) { |
| return MVT::getVectorVT(ElemVT, P == Packing::Normal ? StandardVectorWidth |
| : PackedVectorWidth); |
| } |
| |
| Packing getTypePacking(EVT VT) { |
| assert(VT.isVector()); |
| return isPackedVectorType(VT) ? Packing::Dense : Packing::Normal; |
| } |
| |
| bool isMaskType(EVT SomeVT) { |
| if (!SomeVT.isVector()) |
| return false; |
| return SomeVT.getVectorElementType() == MVT::i1; |
| } |
| |
| bool isMaskArithmetic(SDValue Op) { |
| switch (Op.getOpcode()) { |
| default: |
| return false; |
| case ISD::AND: |
| case ISD::XOR: |
| case ISD::OR: |
| return isMaskType(Op.getValueType()); |
| } |
| } |
| |
| /// \returns the VVP_* SDNode opcode corresponsing to \p OC. |
| std::optional<unsigned> getVVPOpcode(unsigned Opcode) { |
| switch (Opcode) { |
| case ISD::MLOAD: |
| return VEISD::VVP_LOAD; |
| case ISD::MSTORE: |
| return VEISD::VVP_STORE; |
| #define HANDLE_VP_TO_VVP(VPOPC, VVPNAME) \ |
| case ISD::VPOPC: \ |
| return VEISD::VVPNAME; |
| #define ADD_VVP_OP(VVPNAME, SDNAME) \ |
| case VEISD::VVPNAME: \ |
| case ISD::SDNAME: \ |
| return VEISD::VVPNAME; |
| #include "VVPNodes.def" |
| // TODO: Map those in VVPNodes.def too |
| case ISD::EXPERIMENTAL_VP_STRIDED_LOAD: |
| return VEISD::VVP_LOAD; |
| case ISD::EXPERIMENTAL_VP_STRIDED_STORE: |
| return VEISD::VVP_STORE; |
| } |
| return std::nullopt; |
| } |
| |
| bool maySafelyIgnoreMask(SDValue Op) { |
| auto VVPOpc = getVVPOpcode(Op->getOpcode()); |
| auto Opc = VVPOpc.value_or(Op->getOpcode()); |
| |
| switch (Opc) { |
| case VEISD::VVP_SDIV: |
| case VEISD::VVP_UDIV: |
| case VEISD::VVP_FDIV: |
| case VEISD::VVP_SELECT: |
| return false; |
| |
| default: |
| return true; |
| } |
| } |
| |
| bool supportsPackedMode(unsigned Opcode, EVT IdiomVT) { |
| bool IsPackedOp = isPackedVectorType(IdiomVT); |
| bool IsMaskOp = isMaskType(IdiomVT); |
| switch (Opcode) { |
| default: |
| return false; |
| |
| case VEISD::VEC_BROADCAST: |
| return true; |
| #define REGISTER_PACKED(VVP_NAME) case VEISD::VVP_NAME: |
| #include "VVPNodes.def" |
| return IsPackedOp && !IsMaskOp; |
| } |
| } |
| |
| bool isPackingSupportOpcode(unsigned Opc) { |
| switch (Opc) { |
| case VEISD::VEC_PACK: |
| case VEISD::VEC_UNPACK_LO: |
| case VEISD::VEC_UNPACK_HI: |
| return true; |
| } |
| return false; |
| } |
| |
| bool isVVPOrVEC(unsigned Opcode) { |
| switch (Opcode) { |
| case VEISD::VEC_BROADCAST: |
| #define ADD_VVP_OP(VVPNAME, ...) case VEISD::VVPNAME: |
| #include "VVPNodes.def" |
| return true; |
| } |
| return false; |
| } |
| |
| bool isVVPUnaryOp(unsigned VVPOpcode) { |
| switch (VVPOpcode) { |
| #define ADD_UNARY_VVP_OP(VVPNAME, ...) \ |
| case VEISD::VVPNAME: \ |
| return true; |
| #include "VVPNodes.def" |
| } |
| return false; |
| } |
| |
| bool isVVPBinaryOp(unsigned VVPOpcode) { |
| switch (VVPOpcode) { |
| #define ADD_BINARY_VVP_OP(VVPNAME, ...) \ |
| case VEISD::VVPNAME: \ |
| return true; |
| #include "VVPNodes.def" |
| } |
| return false; |
| } |
| |
| bool isVVPReductionOp(unsigned Opcode) { |
| switch (Opcode) { |
| #define ADD_REDUCE_VVP_OP(VVP_NAME, SDNAME) case VEISD::VVP_NAME: |
| #include "VVPNodes.def" |
| return true; |
| } |
| return false; |
| } |
| |
| // Return the AVL operand position for this VVP or VEC Op. |
| std::optional<int> getAVLPos(unsigned Opc) { |
| // This is only available for VP SDNodes |
| auto PosOpt = ISD::getVPExplicitVectorLengthIdx(Opc); |
| if (PosOpt) |
| return *PosOpt; |
| |
| // VVP Opcodes. |
| if (isVVPBinaryOp(Opc)) |
| return 3; |
| |
| // VM Opcodes. |
| switch (Opc) { |
| case VEISD::VEC_BROADCAST: |
| return 1; |
| case VEISD::VVP_SELECT: |
| return 3; |
| case VEISD::VVP_LOAD: |
| return 4; |
| case VEISD::VVP_STORE: |
| return 5; |
| } |
| |
| return std::nullopt; |
| } |
| |
| std::optional<int> getMaskPos(unsigned Opc) { |
| // This is only available for VP SDNodes |
| auto PosOpt = ISD::getVPMaskIdx(Opc); |
| if (PosOpt) |
| return *PosOpt; |
| |
| // VVP Opcodes. |
| if (isVVPBinaryOp(Opc)) |
| return 2; |
| |
| // Other opcodes. |
| switch (Opc) { |
| case ISD::MSTORE: |
| return 4; |
| case ISD::MLOAD: |
| return 3; |
| case VEISD::VVP_SELECT: |
| return 2; |
| } |
| |
| return std::nullopt; |
| } |
| |
| bool isLegalAVL(SDValue AVL) { return AVL->getOpcode() == VEISD::LEGALAVL; } |
| |
| /// Node Properties { |
| |
| SDValue getNodeChain(SDValue Op) { |
| if (MemSDNode *MemN = dyn_cast<MemSDNode>(Op.getNode())) |
| return MemN->getChain(); |
| |
| switch (Op->getOpcode()) { |
| case VEISD::VVP_LOAD: |
| case VEISD::VVP_STORE: |
| return Op->getOperand(0); |
| } |
| return SDValue(); |
| } |
| |
| SDValue getMemoryPtr(SDValue Op) { |
| if (auto *MemN = dyn_cast<MemSDNode>(Op.getNode())) |
| return MemN->getBasePtr(); |
| |
| switch (Op->getOpcode()) { |
| case VEISD::VVP_LOAD: |
| return Op->getOperand(1); |
| case VEISD::VVP_STORE: |
| return Op->getOperand(2); |
| } |
| return SDValue(); |
| } |
| |
| std::optional<EVT> getIdiomaticVectorType(SDNode *Op) { |
| unsigned OC = Op->getOpcode(); |
| |
| // For memory ops -> the transfered data type |
| if (auto MemN = dyn_cast<MemSDNode>(Op)) |
| return MemN->getMemoryVT(); |
| |
| switch (OC) { |
| // Standard ISD. |
| case ISD::SELECT: // not aliased with VVP_SELECT |
| case ISD::CONCAT_VECTORS: |
| case ISD::EXTRACT_SUBVECTOR: |
| case ISD::VECTOR_SHUFFLE: |
| case ISD::BUILD_VECTOR: |
| case ISD::SCALAR_TO_VECTOR: |
| return Op->getValueType(0); |
| } |
| |
| // Translate to VVP where possible. |
| unsigned OriginalOC = OC; |
| if (auto VVPOpc = getVVPOpcode(OC)) |
| OC = *VVPOpc; |
| |
| if (isVVPReductionOp(OC)) |
| return Op->getOperand(hasReductionStartParam(OriginalOC) ? 1 : 0) |
| .getValueType(); |
| |
| switch (OC) { |
| default: |
| case VEISD::VVP_SETCC: |
| return Op->getOperand(0).getValueType(); |
| |
| case VEISD::VVP_SELECT: |
| #define ADD_BINARY_VVP_OP(VVP_NAME, ...) case VEISD::VVP_NAME: |
| #include "VVPNodes.def" |
| return Op->getValueType(0); |
| |
| case VEISD::VVP_LOAD: |
| return Op->getValueType(0); |
| |
| case VEISD::VVP_STORE: |
| return Op->getOperand(1)->getValueType(0); |
| |
| // VEC |
| case VEISD::VEC_BROADCAST: |
| return Op->getValueType(0); |
| } |
| } |
| |
| SDValue getLoadStoreStride(SDValue Op, VECustomDAG &CDAG) { |
| switch (Op->getOpcode()) { |
| case VEISD::VVP_STORE: |
| return Op->getOperand(3); |
| case VEISD::VVP_LOAD: |
| return Op->getOperand(2); |
| } |
| |
| if (auto *StoreN = dyn_cast<VPStridedStoreSDNode>(Op.getNode())) |
| return StoreN->getStride(); |
| if (auto *StoreN = dyn_cast<VPStridedLoadSDNode>(Op.getNode())) |
| return StoreN->getStride(); |
| |
| if (isa<MemSDNode>(Op.getNode())) { |
| // Regular MLOAD/MSTORE/LOAD/STORE |
| // No stride argument -> use the contiguous element size as stride. |
| uint64_t ElemStride = getIdiomaticVectorType(Op.getNode()) |
| ->getVectorElementType() |
| .getStoreSize(); |
| return CDAG.getConstant(ElemStride, MVT::i64); |
| } |
| return SDValue(); |
| } |
| |
| SDValue getGatherScatterIndex(SDValue Op) { |
| if (auto *N = dyn_cast<MaskedGatherScatterSDNode>(Op.getNode())) |
| return N->getIndex(); |
| if (auto *N = dyn_cast<VPGatherScatterSDNode>(Op.getNode())) |
| return N->getIndex(); |
| return SDValue(); |
| } |
| |
| SDValue getGatherScatterScale(SDValue Op) { |
| if (auto *N = dyn_cast<MaskedGatherScatterSDNode>(Op.getNode())) |
| return N->getScale(); |
| if (auto *N = dyn_cast<VPGatherScatterSDNode>(Op.getNode())) |
| return N->getScale(); |
| return SDValue(); |
| } |
| |
| SDValue getStoredValue(SDValue Op) { |
| switch (Op->getOpcode()) { |
| case ISD::EXPERIMENTAL_VP_STRIDED_STORE: |
| case VEISD::VVP_STORE: |
| return Op->getOperand(1); |
| } |
| if (auto *StoreN = dyn_cast<StoreSDNode>(Op.getNode())) |
| return StoreN->getValue(); |
| if (auto *StoreN = dyn_cast<MaskedStoreSDNode>(Op.getNode())) |
| return StoreN->getValue(); |
| if (auto *StoreN = dyn_cast<VPStridedStoreSDNode>(Op.getNode())) |
| return StoreN->getValue(); |
| if (auto *StoreN = dyn_cast<VPStoreSDNode>(Op.getNode())) |
| return StoreN->getValue(); |
| if (auto *StoreN = dyn_cast<MaskedScatterSDNode>(Op.getNode())) |
| return StoreN->getValue(); |
| if (auto *StoreN = dyn_cast<VPScatterSDNode>(Op.getNode())) |
| return StoreN->getValue(); |
| return SDValue(); |
| } |
| |
| SDValue getNodePassthru(SDValue Op) { |
| if (auto *N = dyn_cast<MaskedLoadSDNode>(Op.getNode())) |
| return N->getPassThru(); |
| if (auto *N = dyn_cast<MaskedGatherSDNode>(Op.getNode())) |
| return N->getPassThru(); |
| |
| return SDValue(); |
| } |
| |
| bool hasReductionStartParam(unsigned OPC) { |
| // TODO: Ordered reduction opcodes. |
| if (ISD::isVPReduction(OPC)) |
| return true; |
| return false; |
| } |
| |
| unsigned getScalarReductionOpcode(unsigned VVPOC, bool IsMask) { |
| assert(!IsMask && "Mask reduction isel"); |
| |
| switch (VVPOC) { |
| #define HANDLE_VVP_REDUCE_TO_SCALAR(VVP_RED_ISD, REDUCE_ISD) \ |
| case VEISD::VVP_RED_ISD: \ |
| return ISD::REDUCE_ISD; |
| #include "VVPNodes.def" |
| default: |
| break; |
| } |
| llvm_unreachable("Cannot not scalarize this reduction Opcode!"); |
| } |
| |
| /// } Node Properties |
| |
| SDValue getNodeAVL(SDValue Op) { |
| auto PosOpt = getAVLPos(Op->getOpcode()); |
| return PosOpt ? Op->getOperand(*PosOpt) : SDValue(); |
| } |
| |
| SDValue getNodeMask(SDValue Op) { |
| auto PosOpt = getMaskPos(Op->getOpcode()); |
| return PosOpt ? Op->getOperand(*PosOpt) : SDValue(); |
| } |
| |
| std::pair<SDValue, bool> getAnnotatedNodeAVL(SDValue Op) { |
| SDValue AVL = getNodeAVL(Op); |
| if (!AVL) |
| return {SDValue(), true}; |
| if (isLegalAVL(AVL)) |
| return {AVL->getOperand(0), true}; |
| return {AVL, false}; |
| } |
| |
| SDValue VECustomDAG::getConstant(uint64_t Val, EVT VT, bool IsTarget, |
| bool IsOpaque) const { |
| return DAG.getConstant(Val, DL, VT, IsTarget, IsOpaque); |
| } |
| |
| SDValue VECustomDAG::getConstantMask(Packing Packing, bool AllTrue) const { |
| auto MaskVT = getLegalVectorType(Packing, MVT::i1); |
| |
| // VEISelDAGtoDAG will replace this pattern with the constant-true VM. |
| auto TrueVal = DAG.getConstant(-1, DL, MVT::i32); |
| auto AVL = getConstant(MaskVT.getVectorNumElements(), MVT::i32); |
| auto Res = getNode(VEISD::VEC_BROADCAST, MaskVT, {TrueVal, AVL}); |
| if (AllTrue) |
| return Res; |
| |
| return DAG.getNOT(DL, Res, Res.getValueType()); |
| } |
| |
| SDValue VECustomDAG::getMaskBroadcast(EVT ResultVT, SDValue Scalar, |
| SDValue AVL) const { |
| // Constant mask splat. |
| if (auto BcConst = dyn_cast<ConstantSDNode>(Scalar)) |
| return getConstantMask(getTypePacking(ResultVT), |
| BcConst->getSExtValue() != 0); |
| |
| // Expand the broadcast to a vector comparison. |
| auto ScalarBoolVT = Scalar.getSimpleValueType(); |
| assert(ScalarBoolVT == MVT::i32); |
| |
| // Cast to i32 ty. |
| SDValue CmpElem = DAG.getSExtOrTrunc(Scalar, DL, MVT::i32); |
| unsigned ElemCount = ResultVT.getVectorNumElements(); |
| MVT CmpVecTy = MVT::getVectorVT(ScalarBoolVT, ElemCount); |
| |
| // Broadcast to vector. |
| SDValue BCVec = |
| DAG.getNode(VEISD::VEC_BROADCAST, DL, CmpVecTy, {CmpElem, AVL}); |
| SDValue ZeroVec = |
| getBroadcast(CmpVecTy, {DAG.getConstant(0, DL, ScalarBoolVT)}, AVL); |
| |
| MVT BoolVecTy = MVT::getVectorVT(MVT::i1, ElemCount); |
| |
| // Broadcast(Data) != Broadcast(0) |
| // TODO: Use a VVP operation for this. |
| return DAG.getSetCC(DL, BoolVecTy, BCVec, ZeroVec, ISD::CondCode::SETNE); |
| } |
| |
| SDValue VECustomDAG::getBroadcast(EVT ResultVT, SDValue Scalar, |
| SDValue AVL) const { |
| assert(ResultVT.isVector()); |
| auto ScaVT = Scalar.getValueType(); |
| |
| if (isMaskType(ResultVT)) |
| return getMaskBroadcast(ResultVT, Scalar, AVL); |
| |
| if (isPackedVectorType(ResultVT)) { |
| // v512x packed mode broadcast |
| // Replicate the scalar reg (f32 or i32) onto the opposing half of the full |
| // scalar register. If it's an I64 type, assume that this has already |
| // happened. |
| if (ScaVT == MVT::f32) { |
| Scalar = getNode(VEISD::REPL_F32, MVT::i64, Scalar); |
| } else if (ScaVT == MVT::i32) { |
| Scalar = getNode(VEISD::REPL_I32, MVT::i64, Scalar); |
| } |
| } |
| |
| return getNode(VEISD::VEC_BROADCAST, ResultVT, {Scalar, AVL}); |
| } |
| |
| SDValue VECustomDAG::annotateLegalAVL(SDValue AVL) const { |
| if (isLegalAVL(AVL)) |
| return AVL; |
| return getNode(VEISD::LEGALAVL, AVL.getValueType(), AVL); |
| } |
| |
| SDValue VECustomDAG::getUnpack(EVT DestVT, SDValue Vec, PackElem Part, |
| SDValue AVL) const { |
| assert(getAnnotatedNodeAVL(AVL).second && "Expected a pack-legalized AVL"); |
| |
| // TODO: Peek through VEC_PACK and VEC_BROADCAST(REPL_<sth> ..) operands. |
| unsigned OC = |
| (Part == PackElem::Lo) ? VEISD::VEC_UNPACK_LO : VEISD::VEC_UNPACK_HI; |
| return DAG.getNode(OC, DL, DestVT, Vec, AVL); |
| } |
| |
| SDValue VECustomDAG::getPack(EVT DestVT, SDValue LoVec, SDValue HiVec, |
| SDValue AVL) const { |
| assert(getAnnotatedNodeAVL(AVL).second && "Expected a pack-legalized AVL"); |
| |
| // TODO: Peek through VEC_UNPACK_LO|HI operands. |
| return DAG.getNode(VEISD::VEC_PACK, DL, DestVT, LoVec, HiVec, AVL); |
| } |
| |
| VETargetMasks VECustomDAG::getTargetSplitMask(SDValue RawMask, SDValue RawAVL, |
| PackElem Part) const { |
| // Adjust AVL for this part |
| SDValue NewAVL; |
| SDValue OneV = getConstant(1, MVT::i32); |
| if (Part == PackElem::Hi) |
| NewAVL = getNode(ISD::ADD, MVT::i32, {RawAVL, OneV}); |
| else |
| NewAVL = RawAVL; |
| NewAVL = getNode(ISD::SRL, MVT::i32, {NewAVL, OneV}); |
| |
| NewAVL = annotateLegalAVL(NewAVL); |
| |
| // Legalize Mask (unpack or all-true) |
| SDValue NewMask; |
| if (!RawMask) |
| NewMask = getConstantMask(Packing::Normal, true); |
| else |
| NewMask = getUnpack(MVT::v256i1, RawMask, Part, NewAVL); |
| |
| return VETargetMasks(NewMask, NewAVL); |
| } |
| |
| SDValue VECustomDAG::getSplitPtrOffset(SDValue Ptr, SDValue ByteStride, |
| PackElem Part) const { |
| // High starts at base ptr but has more significant bits in the 64bit vector |
| // element. |
| if (Part == PackElem::Hi) |
| return Ptr; |
| return getNode(ISD::ADD, MVT::i64, {Ptr, ByteStride}); |
| } |
| |
| SDValue VECustomDAG::getSplitPtrStride(SDValue PackStride) const { |
| if (auto ConstBytes = dyn_cast<ConstantSDNode>(PackStride)) |
| return getConstant(2 * ConstBytes->getSExtValue(), MVT::i64); |
| return getNode(ISD::SHL, MVT::i64, {PackStride, getConstant(1, MVT::i32)}); |
| } |
| |
| SDValue VECustomDAG::getGatherScatterAddress(SDValue BasePtr, SDValue Scale, |
| SDValue Index, SDValue Mask, |
| SDValue AVL) const { |
| EVT IndexVT = Index.getValueType(); |
| |
| // Apply scale. |
| SDValue ScaledIndex; |
| if (!Scale || isOneConstant(Scale)) |
| ScaledIndex = Index; |
| else { |
| SDValue ScaleBroadcast = getBroadcast(IndexVT, Scale, AVL); |
| ScaledIndex = |
| getNode(VEISD::VVP_MUL, IndexVT, {Index, ScaleBroadcast, Mask, AVL}); |
| } |
| |
| // Add basePtr. |
| if (isNullConstant(BasePtr)) |
| return ScaledIndex; |
| |
| // re-constitute pointer vector (basePtr + index * scale) |
| SDValue BaseBroadcast = getBroadcast(IndexVT, BasePtr, AVL); |
| auto ResPtr = |
| getNode(VEISD::VVP_ADD, IndexVT, {BaseBroadcast, ScaledIndex, Mask, AVL}); |
| return ResPtr; |
| } |
| |
| SDValue VECustomDAG::getLegalReductionOpVVP(unsigned VVPOpcode, EVT ResVT, |
| SDValue StartV, SDValue VectorV, |
| SDValue Mask, SDValue AVL, |
| SDNodeFlags Flags) const { |
| |
| // Optionally attach the start param with a scalar op (where it is |
| // unsupported). |
| bool scalarizeStartParam = StartV && !hasReductionStartParam(VVPOpcode); |
| bool IsMaskReduction = isMaskType(VectorV.getValueType()); |
| assert(!IsMaskReduction && "TODO Implement"); |
| auto AttachStartValue = [&](SDValue ReductionResV) { |
| if (!scalarizeStartParam) |
| return ReductionResV; |
| auto ScalarOC = getScalarReductionOpcode(VVPOpcode, IsMaskReduction); |
| return getNode(ScalarOC, ResVT, {StartV, ReductionResV}); |
| }; |
| |
| // Fixup: Always Use sequential 'fmul' reduction. |
| if (!scalarizeStartParam && StartV) { |
| assert(hasReductionStartParam(VVPOpcode)); |
| return AttachStartValue( |
| getNode(VVPOpcode, ResVT, {StartV, VectorV, Mask, AVL}, Flags)); |
| } else |
| return AttachStartValue( |
| getNode(VVPOpcode, ResVT, {VectorV, Mask, AVL}, Flags)); |
| } |
| |
| } // namespace llvm |