| //===- SLPCompatibilityAnalysis.cpp - SLP same-opcode helpers -------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SLPCompatibilityAnalysis.h" |
| #include "SLPUtils.h" |
| |
| #include "llvm/ADT/APInt.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/ADT/SetVector.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/SmallVectorExtras.h" |
| #include "llvm/ADT/bit.h" |
| #include "llvm/Analysis/VectorUtils.h" |
| #include "llvm/IR/Constants.h" |
| #include "llvm/IR/InstrTypes.h" |
| #include "llvm/IR/Instruction.h" |
| #include "llvm/IR/Instructions.h" |
| #include "llvm/IR/IntrinsicInst.h" |
| #include "llvm/IR/Intrinsics.h" |
| #include "llvm/IR/PatternMatch.h" |
| #include "llvm/IR/Value.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/ErrorHandling.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <optional> |
| #include <utility> |
| |
| using namespace llvm; |
| using namespace llvm::PatternMatch; |
| |
| namespace llvm::slpvectorizer { |
| |
| bool isValidForAlternation(unsigned Opcode) { |
| return !Instruction::isIntDivRem(Opcode); |
| } |
| |
| std::pair<Constant *, unsigned> |
| BinOpSameOpcodeHelper::isBinOpWithConstant(const Instruction *I) { |
| [[maybe_unused]] unsigned Opcode = I->getOpcode(); |
| assert(binary_search(SupportedOp, Opcode) && "Unsupported opcode."); |
| (void)SupportedOp; |
| auto *BinOp = cast<BinaryOperator>(I); |
| auto GetConstant = [](Value *V) -> Constant * { |
| if (auto *CI = dyn_cast<ConstantInt>(V)) |
| return CI; |
| return dyn_cast<ConstantFP>(V); |
| }; |
| if (Constant *C = GetConstant(BinOp->getOperand(1))) |
| return {C, 1}; |
| if (!isCommutative(I)) |
| return {nullptr, 0}; |
| if (Constant *C = GetConstant(BinOp->getOperand(0))) |
| return {C, 0}; |
| return {nullptr, 0}; |
| } |
| |
| bool BinOpSameOpcodeHelper::InterchangeableInfo::trySet( |
| MaskType OpcodeInMaskForm, MaskType InterchangeableMask) { |
| if (Mask & InterchangeableMask) { |
| SeenBefore |= OpcodeInMaskForm; |
| Mask &= InterchangeableMask; |
| return true; |
| } |
| return false; |
| } |
| |
| unsigned BinOpSameOpcodeHelper::InterchangeableInfo::getOpcode() const { |
| MaskType Candidate = Mask & SeenBefore; |
| if (Candidate & MainOpBIT) |
| return I->getOpcode(); |
| if (Candidate & ShlBIT) |
| return Instruction::Shl; |
| if (Candidate & AShrBIT) |
| return Instruction::AShr; |
| if (Candidate & MulBIT) |
| return Instruction::Mul; |
| if (Candidate & AddBIT) |
| return Instruction::Add; |
| if (Candidate & SubBIT) |
| return Instruction::Sub; |
| if (Candidate & FAddBIT) |
| return Instruction::FAdd; |
| if (Candidate & FSubBIT) |
| return Instruction::FSub; |
| if (Candidate & AndBIT) |
| return Instruction::And; |
| if (Candidate & OrBIT) |
| return Instruction::Or; |
| if (Candidate & XorBIT) |
| return Instruction::Xor; |
| llvm_unreachable("Cannot find interchangeable instruction."); |
| } |
| |
| bool BinOpSameOpcodeHelper::InterchangeableInfo::hasCandidateOpcode( |
| unsigned Opcode) const { |
| MaskType Candidate = Mask & SeenBefore; |
| switch (Opcode) { |
| case Instruction::Shl: |
| return Candidate & ShlBIT; |
| case Instruction::AShr: |
| return Candidate & AShrBIT; |
| case Instruction::Mul: |
| return Candidate & MulBIT; |
| case Instruction::Add: |
| return Candidate & AddBIT; |
| case Instruction::Sub: |
| return Candidate & SubBIT; |
| case Instruction::And: |
| return Candidate & AndBIT; |
| case Instruction::Or: |
| return Candidate & OrBIT; |
| case Instruction::Xor: |
| return Candidate & XorBIT; |
| case Instruction::FAdd: |
| return Candidate & FAddBIT; |
| case Instruction::FSub: |
| return Candidate & FSubBIT; |
| case Instruction::LShr: |
| case Instruction::FMul: |
| case Instruction::SDiv: |
| case Instruction::UDiv: |
| case Instruction::FDiv: |
| case Instruction::SRem: |
| case Instruction::URem: |
| case Instruction::FRem: |
| return false; |
| default: |
| break; |
| } |
| llvm_unreachable("Cannot find interchangeable instruction."); |
| } |
| |
| SmallVector<Value *> BinOpSameOpcodeHelper::InterchangeableInfo::getOperand( |
| const Instruction *To) const { |
| unsigned ToOpcode = To->getOpcode(); |
| unsigned FromOpcode = I->getOpcode(); |
| if (FromOpcode == ToOpcode) |
| return SmallVector<Value *>(I->operands()); |
| assert(binary_search(SupportedOp, ToOpcode) && "Unsupported opcode."); |
| auto [C, Pos] = isBinOpWithConstant(I); |
| Type *RHSType = I->getOperand(Pos)->getType(); |
| Constant *RHS; |
| if (auto *CFP = dyn_cast<ConstantFP>(C)) { |
| // fsub(x, c) == fadd(x, -c) for every FP constant c, since IEEE 754 |
| // defines subtraction as addition of the negated operand. |
| assert(is_contained({Instruction::FAdd, Instruction::FSub}, ToOpcode) && |
| "Cannot convert the instruction."); |
| RHS = ConstantFP::get(RHSType, -CFP->getValueAPF()); |
| } else { |
| auto *CI = cast<ConstantInt>(C); |
| const APInt &FromCIValue = CI->getValue(); |
| unsigned FromCIValueBitWidth = FromCIValue.getBitWidth(); |
| switch (FromOpcode) { |
| case Instruction::Shl: |
| if (ToOpcode == Instruction::Add && FromCIValue.isOne()) |
| return {I->getOperand(0), I->getOperand(0)}; |
| if (ToOpcode == Instruction::Mul) { |
| RHS = ConstantInt::get(RHSType, |
| APInt::getOneBitSet(FromCIValueBitWidth, |
| FromCIValue.getZExtValue())); |
| } else { |
| assert(FromCIValue.isZero() && "Cannot convert the instruction."); |
| RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType, |
| /*AllowRHSConstant=*/true); |
| } |
| break; |
| case Instruction::Mul: |
| assert(FromCIValue.isPowerOf2() && "Cannot convert the instruction."); |
| if (ToOpcode == Instruction::Shl) { |
| RHS = ConstantInt::get( |
| RHSType, APInt(FromCIValueBitWidth, FromCIValue.logBase2())); |
| } else { |
| assert(FromCIValue.isOne() && "Cannot convert the instruction."); |
| RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType, |
| /*AllowRHSConstant=*/true); |
| } |
| break; |
| case Instruction::Add: |
| case Instruction::Sub: |
| if (FromCIValue.isZero()) { |
| RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType, |
| /*AllowRHSConstant=*/true); |
| } else { |
| assert(is_contained({Instruction::Add, Instruction::Sub}, ToOpcode) && |
| "Cannot convert the instruction."); |
| APInt NegatedVal = APInt(FromCIValue); |
| NegatedVal.negate(); |
| RHS = ConstantInt::get(RHSType, NegatedVal); |
| } |
| break; |
| case Instruction::And: |
| assert(FromCIValue.isAllOnes() && "Cannot convert the instruction."); |
| RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType, |
| /*AllowRHSConstant=*/true); |
| break; |
| default: |
| assert(FromCIValue.isZero() && "Cannot convert the instruction."); |
| RHS = ConstantExpr::getBinOpIdentity(ToOpcode, RHSType, |
| /*AllowRHSConstant=*/true); |
| break; |
| } |
| } |
| Value *LHS = I->getOperand(1 - Pos); |
| // If the target opcode is non-commutative (e.g., shl, sub), |
| // force the variable to the left and the constant to the right. |
| if (Pos == 1 || !Instruction::isCommutative(ToOpcode)) |
| return SmallVector<Value *>({LHS, RHS}); |
| |
| return SmallVector<Value *>({RHS, LHS}); |
| } |
| |
| bool BinOpSameOpcodeHelper::isValidForAlternation(const Instruction *I) const { |
| return slpvectorizer::isValidForAlternation(MainOp.I->getOpcode()) && |
| slpvectorizer::isValidForAlternation(I->getOpcode()); |
| } |
| |
| bool BinOpSameOpcodeHelper::initializeAltOp(const Instruction *I) { |
| if (AltOp.I) |
| return true; |
| if (!isValidForAlternation(I)) |
| return false; |
| AltOp.I = I; |
| return true; |
| } |
| |
| bool BinOpSameOpcodeHelper::add(const Instruction *I) { |
| assert(isa<BinaryOperator>(I) && |
| "BinOpSameOpcodeHelper only accepts BinaryOperator."); |
| unsigned Opcode = I->getOpcode(); |
| MaskType OpcodeInMaskForm; |
| // Prefer Shl, AShr, Mul, Add, Sub, And, Or, Xor, FAdd and FSub over |
| // MainOp. |
| switch (Opcode) { |
| case Instruction::Shl: |
| OpcodeInMaskForm = ShlBIT; |
| break; |
| case Instruction::AShr: |
| OpcodeInMaskForm = AShrBIT; |
| break; |
| case Instruction::Mul: |
| OpcodeInMaskForm = MulBIT; |
| break; |
| case Instruction::Add: |
| OpcodeInMaskForm = AddBIT; |
| break; |
| case Instruction::Sub: |
| OpcodeInMaskForm = SubBIT; |
| break; |
| case Instruction::And: |
| OpcodeInMaskForm = AndBIT; |
| break; |
| case Instruction::Or: |
| OpcodeInMaskForm = OrBIT; |
| break; |
| case Instruction::Xor: |
| OpcodeInMaskForm = XorBIT; |
| break; |
| case Instruction::FAdd: |
| OpcodeInMaskForm = FAddBIT; |
| break; |
| case Instruction::FSub: |
| OpcodeInMaskForm = FSubBIT; |
| break; |
| default: |
| return MainOp.equal(Opcode) || (initializeAltOp(I) && AltOp.equal(Opcode)); |
| } |
| MaskType InterchangeableMask = OpcodeInMaskForm; |
| auto [C, Pos] = isBinOpWithConstant(I); |
| if (auto *CI = dyn_cast_or_null<ConstantInt>(C)) { |
| constexpr MaskType CanBeAll = |
| XorBIT | OrBIT | AndBIT | SubBIT | AddBIT | MulBIT | AShrBIT | ShlBIT; |
| const APInt &CIValue = CI->getValue(); |
| switch (Opcode) { |
| case Instruction::Shl: |
| if (CIValue.ult(CIValue.getBitWidth())) |
| InterchangeableMask = CIValue.isZero() ? CanBeAll : MulBIT | ShlBIT; |
| if (CIValue.isOne()) |
| InterchangeableMask |= AddBIT; |
| break; |
| case Instruction::Mul: |
| if (CIValue.isOne()) { |
| InterchangeableMask = CanBeAll; |
| break; |
| } |
| if (CIValue.isPowerOf2()) |
| InterchangeableMask = MulBIT | ShlBIT; |
| break; |
| case Instruction::Add: |
| case Instruction::Sub: |
| InterchangeableMask = CIValue.isZero() ? CanBeAll : SubBIT | AddBIT; |
| break; |
| case Instruction::And: |
| if (CIValue.isAllOnes()) |
| InterchangeableMask = CanBeAll; |
| break; |
| case Instruction::Xor: |
| if (CIValue.isZero()) |
| InterchangeableMask = XorBIT | OrBIT | SubBIT | AddBIT; |
| break; |
| default: |
| if (CIValue.isZero()) |
| InterchangeableMask = CanBeAll; |
| break; |
| } |
| } else if (C && Pos == 1) { |
| // FAdd/FSub with a constant RHS: negating the constant always |
| // converts one into the other, so no value check is needed. A |
| // constant LHS (Pos == 0, e.g. "0.0 - x") is excluded: unlike a |
| // constant RHS, it cannot be moved to the other opcode without also |
| // swapping the variable operand, which would misalign it against |
| // lanes that keep their native opcode (their variable operand stays |
| // on the other side). |
| InterchangeableMask = FSubBIT | FAddBIT; |
| } |
| return MainOp.trySet(OpcodeInMaskForm, InterchangeableMask) || |
| (initializeAltOp(I) && |
| AltOp.trySet(OpcodeInMaskForm, InterchangeableMask)); |
| } |
| |
| /// If the comparison (Pred, X, C) is a single-element or single-complement |
| /// range check, returns its boundary family: false + K for the singleton |
| /// {K} (eq forms), true + K for the complement of {K} (ne forms). |
| static std::optional<std::pair<bool, APInt>> |
| getCmpBoundaryFamily(CmpInst::Predicate Pred, const APInt &C) { |
| const unsigned BW = C.getBitWidth(); |
| const bool IsSigned = CmpInst::isSigned(Pred); |
| const APInt Min = IsSigned ? APInt::getSignedMinValue(BW) : APInt(BW, 0); |
| const APInt Max = |
| IsSigned ? APInt::getSignedMaxValue(BW) : APInt::getMaxValue(BW); |
| switch (Pred) { |
| case CmpInst::ICMP_EQ: |
| return std::make_pair(false, C); |
| case CmpInst::ICMP_NE: |
| return std::make_pair(true, C); |
| case CmpInst::ICMP_ULT: |
| case CmpInst::ICMP_SLT: |
| if (C == Min + 1) |
| return std::make_pair(false, Min); |
| if (C == Max) |
| return std::make_pair(true, C); |
| break; |
| case CmpInst::ICMP_ULE: |
| case CmpInst::ICMP_SLE: |
| if (C == Min) |
| return std::make_pair(false, C); |
| if (C == Max - 1) |
| return std::make_pair(true, Max); |
| break; |
| case CmpInst::ICMP_UGT: |
| case CmpInst::ICMP_SGT: |
| if (C == Min) |
| return std::make_pair(true, C); |
| if (C == Max - 1) |
| return std::make_pair(false, Max); |
| break; |
| case CmpInst::ICMP_UGE: |
| case CmpInst::ICMP_SGE: |
| if (C == Min + 1) |
| return std::make_pair(true, Min); |
| if (C == Max) |
| return std::make_pair(false, C); |
| break; |
| default: |
| break; |
| } |
| return std::nullopt; |
| } |
| |
| CmpSamePredicateHelper::MaskType |
| CmpSamePredicateHelper::getFormsMask(CmpInst::Predicate Pred, const APInt &C) { |
| MaskType M = getBit(Pred); |
| std::optional<std::pair<bool, APInt>> Family = getCmpBoundaryFamily(Pred, C); |
| if (!Family) |
| return M; |
| const auto &[IsComplement, K] = *Family; |
| // At each type boundary the two range checks covering exactly {K}; the |
| // complement family uses their inverses, covering everything but {K}. |
| const MaskType LoU = getBit(CmpInst::ICMP_ULT) | getBit(CmpInst::ICMP_ULE); |
| const MaskType HiU = getBit(CmpInst::ICMP_UGT) | getBit(CmpInst::ICMP_UGE); |
| const MaskType LoS = getBit(CmpInst::ICMP_SLT) | getBit(CmpInst::ICMP_SLE); |
| const MaskType HiS = getBit(CmpInst::ICMP_SGT) | getBit(CmpInst::ICMP_SGE); |
| if (K.isZero()) |
| M |= IsComplement ? HiU : LoU; |
| if (K.isMaxValue()) |
| M |= IsComplement ? LoU : HiU; |
| if (K.isMinSignedValue()) |
| M |= IsComplement ? HiS : LoS; |
| if (K.isMaxSignedValue()) |
| M |= IsComplement ? LoS : HiS; |
| return M | getBit(IsComplement ? CmpInst::ICMP_NE : CmpInst::ICMP_EQ); |
| } |
| |
| APInt CmpSamePredicateHelper::getFamilyConstant(bool IsComplement, |
| const APInt &K, |
| CmpInst::Predicate Pred) { |
| if (Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE) |
| return K; |
| // The complement form uses the singleton constant of the inverse |
| // predicate. |
| if (IsComplement) |
| Pred = CmpInst::getInversePredicate(Pred); |
| switch (Pred) { |
| case CmpInst::ICMP_ULT: |
| case CmpInst::ICMP_SLT: |
| return K + 1; |
| case CmpInst::ICMP_UGT: |
| case CmpInst::ICMP_SGT: |
| return K - 1; |
| default: |
| return K; |
| } |
| } |
| |
| bool CmpSamePredicateHelper::add(const ICmpInst *CI) { |
| MaskType LaneMask = getBit(CI->getPredicate()); |
| if (auto *C = dyn_cast<ConstantInt>(CI->getOperand(1))) |
| LaneMask = getFormsMask(CI->getPredicate(), C->getValue()); |
| SeenBefore |= getBit(CI->getPredicate()); |
| Mask &= LaneMask; |
| return Mask != 0; |
| } |
| |
| CmpInst::Predicate |
| CmpSamePredicateHelper::getPredicate(const ICmpInst *Preferred) const { |
| MaskType Candidate = Mask & SeenBefore; |
| if (!Candidate) |
| return CmpInst::BAD_ICMP_PREDICATE; |
| if (Candidate & getBit(Preferred->getPredicate())) |
| return Preferred->getPredicate(); |
| return static_cast<CmpInst::Predicate>(CmpInst::ICMP_EQ + |
| countr_zero(Candidate)); |
| } |
| |
| CmpInst::Predicate |
| CmpSamePredicateHelper::getSharedPredicate(ArrayRef<Value *> VL, |
| const ICmpInst *Preferred) { |
| CmpSamePredicateHelper Helper; |
| if (!all_of(VL, [&](Value *V) { |
| auto *CI = dyn_cast<ICmpInst>(V); |
| return isa<PoisonValue>(V) || (CI && Helper.add(CI)); |
| })) |
| return CmpInst::BAD_ICMP_PREDICATE; |
| return Helper.getPredicate(Preferred); |
| } |
| |
| bool CmpSamePredicateHelper::canConvertTo(const CmpInst *CI, |
| CmpInst::Predicate Pred) { |
| auto *ICI = dyn_cast<ICmpInst>(CI); |
| if (!ICI || !CmpInst::isIntPredicate(Pred)) |
| return false; |
| if (ICI->getPredicate() == Pred) |
| return true; |
| auto *C = dyn_cast<ConstantInt>(ICI->getOperand(1)); |
| return C && |
| (getFormsMask(ICI->getPredicate(), C->getValue()) & getBit(Pred)) != 0; |
| } |
| |
| ConstantInt * |
| CmpSamePredicateHelper::getAdjustedConstant(const CmpInst *CI, |
| CmpInst::Predicate Pred) { |
| if (!canConvertTo(CI, Pred)) |
| return nullptr; |
| auto *ICI = cast<ICmpInst>(CI); |
| if (ICI->getPredicate() == Pred) |
| return nullptr; |
| auto *C = cast<ConstantInt>(ICI->getOperand(1)); |
| std::optional<std::pair<bool, APInt>> Family = |
| getCmpBoundaryFamily(ICI->getPredicate(), C->getValue()); |
| assert(Family && "Expected a boundary family for a convertible compare."); |
| const auto &[IsComplement, K] = *Family; |
| return ConstantInt::get(CI->getContext(), |
| getFamilyConstant(IsComplement, K, Pred)); |
| } |
| |
| bool InstructionsState::isSameOperation(const Instruction *I, |
| const Instruction *Op) { |
| if (I->getOpcode() != Op->getOpcode()) |
| return false; |
| const auto *II = dyn_cast<IntrinsicInst>(I); |
| const auto *IOp = dyn_cast<IntrinsicInst>(Op); |
| if (II || IOp) |
| return II && IOp && |
| isEquivalentIntrinsicID(II->getIntrinsicID(), |
| IOp->getIntrinsicID()) != |
| Intrinsic::not_intrinsic; |
| return true; |
| } |
| |
| Instruction *InstructionsState::getMatchingMainOpOrAltOp(Instruction *I) const { |
| assert(MainOp && "MainOp cannot be nullptr."); |
| if (isSameOperation(I, MainOp)) |
| return MainOp; |
| if (MainOp->getOpcode() == Instruction::Select && |
| I->getOpcode() == Instruction::ZExt && !isAltShuffle()) |
| return MainOp; |
| // Prefer AltOp instead of interchangeable instruction of MainOp. |
| assert(AltOp && "AltOp cannot be nullptr."); |
| if (isSameOperation(I, AltOp)) |
| return AltOp; |
| // BinOpSameOpcodeHelper handles only BinaryOperators; a call cannot match. |
| if (!I->isBinaryOp() || !MainOp->isBinaryOp()) |
| return nullptr; |
| BinOpSameOpcodeHelper Converter(MainOp); |
| if (!Converter.add(I) || !Converter.add(MainOp)) |
| return nullptr; |
| if (isAltShuffle() && !Converter.hasCandidateOpcode(MainOp->getOpcode())) { |
| BinOpSameOpcodeHelper AltConverter(AltOp); |
| if (AltConverter.add(I) && AltConverter.add(AltOp) && |
| AltConverter.hasCandidateOpcode(AltOp->getOpcode())) |
| return AltOp; |
| } |
| if (Converter.hasAltOp() && !isAltShuffle()) |
| return nullptr; |
| return Converter.hasAltOp() ? AltOp : MainOp; |
| } |
| |
| bool InstructionsState::isMulDivLikeOp() const { |
| constexpr std::array<unsigned, 8> MulDiv = { |
| Instruction::Mul, Instruction::FMul, Instruction::SDiv, |
| Instruction::UDiv, Instruction::FDiv, Instruction::SRem, |
| Instruction::URem, Instruction::FRem}; |
| return is_contained(MulDiv, getOpcode()) && |
| is_contained(MulDiv, getAltOpcode()); |
| } |
| |
| bool InstructionsState::isAddSubLikeOp() const { |
| constexpr std::array<unsigned, 4> AddSub = { |
| Instruction::Add, Instruction::Sub, Instruction::FAdd, Instruction::FSub}; |
| return is_contained(AddSub, getOpcode()) && |
| is_contained(AddSub, getAltOpcode()); |
| } |
| |
| bool InstructionsState::isCopyableElement(Value *V) const { |
| assert(valid() && "InstructionsState is invalid."); |
| if (!HasCopyables) |
| return false; |
| if (isAltShuffle() || getOpcode() == Instruction::GetElementPtr) |
| return false; |
| auto *I = dyn_cast<Instruction>(V); |
| if (!I) |
| return !isa<PoisonValue>(V); |
| if (I->getParent() != MainOp->getParent() && |
| (!isVectorLikeInstWithConstOps(I) || |
| !isVectorLikeInstWithConstOps(MainOp))) |
| return true; |
| if (isSameOperation(I, MainOp)) |
| return false; |
| // BinOpSameOpcodeHelper handles only BinaryOperators; a call is copyable. |
| if (!I->isBinaryOp() || !MainOp->isBinaryOp()) |
| return true; |
| BinOpSameOpcodeHelper Converter(MainOp); |
| return !Converter.add(I) || !Converter.add(MainOp) || Converter.hasAltOp() || |
| !Converter.hasCandidateOpcode(getOpcode()); |
| } |
| |
| bool isAbsorbableFMulOrFAdd(ArrayRef<Value *> VL, Value *V) { |
| auto *I = dyn_cast<Instruction>(V); |
| return I && |
| (I->getOpcode() == Instruction::FMul || |
| I->getOpcode() == Instruction::FAdd) && |
| I->hasOneUse() && none_of(I->operands(), [&](Value *Op) { |
| return is_contained(VL, Op); |
| }); |
| } |
| |
| bool isAbsorbableCopyableFMulOrFAdd(const InstructionsState &S, Value *V) { |
| auto *I = dyn_cast<Instruction>(V); |
| return I && S.isCopyableElement(I) && |
| (I->getOpcode() == Instruction::FMul || |
| I->getOpcode() == Instruction::FAdd) && |
| I->hasOneUse(); |
| } |
| |
| bool hasOnlyAbsorbableCopyableFMulOrFAdds(ArrayRef<Value *> VL) { |
| bool HasFMulOrFAdd = false; |
| for (Value *V : VL) { |
| if (isa<PoisonValue>(V)) |
| continue; |
| auto *I = dyn_cast<Instruction>(V); |
| if (I && RecurrenceDescriptor::isFMulAddIntrinsic(I)) |
| continue; |
| if (!isAbsorbableFMulOrFAdd(VL, V)) |
| return false; |
| HasFMulOrFAdd = true; |
| } |
| return HasFMulOrFAdd; |
| } |
| |
| bool InstructionsState::isExpandedBinOp(Value *V) const { |
| assert(valid() && "InstructionsState is invalid."); |
| if (isCopyableElement(V)) |
| return false; |
| auto *ExpandingOp = dyn_cast<Instruction>(V); |
| if (!ExpandingOp) |
| return false; |
| auto CheckForTransformedOpcode = [](const Instruction *RefOp, |
| const Instruction *ExpandingOp) { |
| switch (RefOp->getOpcode()) { |
| case Instruction::Add: |
| switch (ExpandingOp->getOpcode()) { |
| case Instruction::Shl: |
| return match(ExpandingOp, m_Shl(m_Value(), m_One())); |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| return false; |
| }; |
| // getMatchingMainOpOrAltOp() may legitimately return nullptr, e.g. for a |
| // split node, whose Scalars combine two unrelated operations (main/alt |
| // ops of the split state), so V is not required to match either of them. |
| Instruction *MainOp = getMatchingMainOpOrAltOp(ExpandingOp); |
| if (!MainOp) |
| return false; |
| return CheckForTransformedOpcode(MainOp, ExpandingOp); |
| } |
| |
| bool InstructionsState::isExpandedOperand(Instruction *I, unsigned Idx) const { |
| assert(isExpandedBinOp(I) && "Expected an expanded binop."); |
| switch (I->getOpcode()) { |
| case Instruction::Shl: |
| assert(match(I, m_Shl(m_Value(), m_One())) && "Expected shl x, 1 only."); |
| return Idx == 1; |
| default: |
| llvm_unreachable("Unexpected opcode for an expanded operand."); |
| } |
| } |
| |
| bool InstructionsState::isNonSchedulable(Value *V) const { |
| assert(valid() && "InstructionsState is invalid."); |
| auto *I = dyn_cast<Instruction>(V); |
| if (!HasCopyables) |
| return !I || isa<PHINode>(I) || isVectorLikeInstWithConstOps(I) || |
| doesNotNeedToBeScheduled(V); |
| // MainOp for copyables always schedulable to correctly identify |
| // non-schedulable copyables. |
| if (getMainOp() == V) |
| return false; |
| if (isCopyableElement(V)) { |
| auto IsNonSchedulableCopyableElement = [this](Value *V) { |
| auto *I = dyn_cast<Instruction>(V); |
| return !I || isa<PHINode>(I) || I->getParent() != MainOp->getParent() || |
| (doesNotNeedToBeScheduled(I) && |
| // If the copyable instructions comes after MainOp |
| // (non-schedulable, but used in the block) - cannot vectorize |
| // it, will possibly generate use before def. |
| !MainOp->comesBefore(I)); |
| }; |
| |
| return IsNonSchedulableCopyableElement(V); |
| } |
| return !I || isa<PHINode>(I) || isVectorLikeInstWithConstOps(I) || |
| doesNotNeedToBeScheduled(V); |
| } |
| |
| /// Find an instruction with a specific opcode in VL. |
| /// \param VL Array of values to search through. Must contain only Instructions |
| /// and PoisonValues. |
| /// \param Opcode The instruction opcode to search for |
| /// \returns |
| /// - The first instruction found with matching opcode |
| /// - nullptr if no matching instruction is found |
| static Instruction *findInstructionWithOpcode(ArrayRef<Value *> VL, |
| unsigned Opcode) { |
| for (Value *V : VL) { |
| if (isa<PoisonValue>(V)) |
| continue; |
| assert(isa<Instruction>(V) && "Only accepts PoisonValue and Instruction."); |
| auto *Inst = cast<Instruction>(V); |
| if (Inst->getOpcode() == Opcode) |
| return Inst; |
| } |
| return nullptr; |
| } |
| |
| /// Checks if the provided operands of 2 cmp instructions are compatible, i.e. |
| /// compatible instructions or constants, or just some other regular values. |
| static bool areCompatibleCmpOps(Value *BaseOp0, Value *BaseOp1, Value *Op0, |
| Value *Op1, const TargetLibraryInfo &TLI) { |
| return (isConstant(BaseOp0) && isConstant(Op0)) || |
| (isConstant(BaseOp1) && isConstant(Op1)) || |
| (!isa<Instruction>(BaseOp0) && !isa<Instruction>(Op0) && |
| !isa<Instruction>(BaseOp1) && !isa<Instruction>(Op1)) || |
| BaseOp0 == Op0 || BaseOp1 == Op1 || |
| getSameOpcode({BaseOp0, Op0}, TLI) || |
| getSameOpcode({BaseOp1, Op1}, TLI); |
| } |
| |
| /// \returns true if a compare instruction \p CI has similar "look" and |
| /// same predicate as \p BaseCI, "as is" or with its operands and predicate |
| /// swapped, false otherwise. |
| static bool isCmpSameOrSwapped(const CmpInst *BaseCI, const CmpInst *CI, |
| const TargetLibraryInfo &TLI) { |
| assert(BaseCI->getOperand(0)->getType() == CI->getOperand(0)->getType() && |
| "Assessing comparisons of different types?"); |
| CmpInst::Predicate BasePred = BaseCI->getPredicate(); |
| CmpInst::Predicate Pred = CI->getPredicate(); |
| CmpInst::Predicate SwappedPred = CmpInst::getSwappedPredicate(Pred); |
| |
| Value *BaseOp0 = BaseCI->getOperand(0); |
| Value *BaseOp1 = BaseCI->getOperand(1); |
| Value *Op0 = CI->getOperand(0); |
| Value *Op1 = CI->getOperand(1); |
| |
| return (BasePred == Pred && |
| areCompatibleCmpOps(BaseOp0, BaseOp1, Op0, Op1, TLI)) || |
| (BasePred == SwappedPred && |
| areCompatibleCmpOps(BaseOp0, BaseOp1, Op1, Op0, TLI)); |
| } |
| |
| InstructionsState getSameOpcode(ArrayRef<Value *> VL, |
| const TargetLibraryInfo &TLI) { |
| // Make sure these are all Instructions. |
| if (!all_of(VL, IsaPred<Instruction, PoisonValue>)) |
| return InstructionsState::invalid(); |
| |
| auto *It = find_if(VL, IsaPred<Instruction>); |
| if (It == VL.end()) |
| return InstructionsState::invalid(); |
| |
| Instruction *MainOp = cast<Instruction>(*It); |
| unsigned InstCnt = std::count_if(It, VL.end(), IsaPred<Instruction>); |
| if ((VL.size() > 2 && !isa<PHINode>(MainOp) && InstCnt < VL.size() / 2) || |
| (VL.size() == 2 && InstCnt < 2)) |
| return InstructionsState::invalid(); |
| |
| bool IsCastOp = isa<CastInst>(MainOp); |
| bool IsBinOp = isa<BinaryOperator>(MainOp); |
| bool IsCmpOp = isa<CmpInst>(MainOp); |
| CmpInst::Predicate BasePred = IsCmpOp ? cast<CmpInst>(MainOp)->getPredicate() |
| : CmpInst::BAD_ICMP_PREDICATE; |
| Instruction *AltOp = MainOp; |
| unsigned Opcode = MainOp->getOpcode(); |
| unsigned AltOpcode = Opcode; |
| |
| BinOpSameOpcodeHelper BinOpHelper(MainOp); |
| bool SwappedPredsCompatible = IsCmpOp && [&]() { |
| SetVector<unsigned> UniquePreds, UniqueNonSwappedPreds; |
| UniquePreds.insert(BasePred); |
| UniqueNonSwappedPreds.insert(BasePred); |
| for (Value *V : VL) { |
| auto *I = dyn_cast<CmpInst>(V); |
| if (!I) |
| return false; |
| CmpInst::Predicate CurrentPred = I->getPredicate(); |
| CmpInst::Predicate SwappedCurrentPred = |
| CmpInst::getSwappedPredicate(CurrentPred); |
| UniqueNonSwappedPreds.insert(CurrentPred); |
| if (!UniquePreds.contains(CurrentPred) && |
| !UniquePreds.contains(SwappedCurrentPred)) |
| UniquePreds.insert(CurrentPred); |
| } |
| // Total number of predicates > 2, but if consider swapped predicates |
| // compatible only 2, consider swappable predicates as compatible opcodes, |
| // not alternate. |
| return UniqueNonSwappedPreds.size() > 2 && UniquePreds.size() == 2; |
| }(); |
| // Find the predicate the whole bundle can share, if any, treating |
| // boundary comparisons canonicalized to eq/ne as interchangeable. |
| CmpInst::Predicate InterchangeablePred = CmpInst::BAD_ICMP_PREDICATE; |
| if (IsCmpOp && isa<ICmpInst>(MainOp)) |
| InterchangeablePred = |
| CmpSamePredicateHelper::getSharedPredicate(VL, cast<ICmpInst>(MainOp)); |
| // Check for one alternate opcode from another BinaryOperator. |
| // TODO - generalize to support all operators (types, calls etc.). |
| Intrinsic::ID BaseID = 0; |
| SmallVector<VFInfo, 4> BaseMappings; |
| if (auto *CallBase = dyn_cast<CallInst>(MainOp)) { |
| BaseID = getVectorIntrinsicIDForCall(CallBase, &TLI); |
| BaseMappings = VFDatabase(*CallBase).getMappings(*CallBase); |
| if (!isTriviallyVectorizable(BaseID) && BaseMappings.empty()) |
| return InstructionsState::invalid(); |
| } |
| bool AnyPoison = InstCnt != VL.size(); |
| // Check MainOp too to be sure that it matches the requirements for the |
| // instructions. |
| for (Value *V : iterator_range(It, VL.end())) { |
| auto *I = dyn_cast<Instruction>(V); |
| if (!I) |
| continue; |
| |
| // Cannot combine poison and divisions. |
| // TODO: do some smart analysis of the CallInsts to exclude divide-like |
| // intrinsics/functions only. |
| if (AnyPoison && (I->isIntDivRem() || I->isFPDivRem() || isa<CallInst>(I))) |
| return InstructionsState::invalid(); |
| unsigned InstOpcode = I->getOpcode(); |
| if (IsBinOp && isa<BinaryOperator>(I)) { |
| if (BinOpHelper.add(I)) |
| continue; |
| } else if (IsCastOp && isa<CastInst>(I)) { |
| Value *Op0 = MainOp->getOperand(0); |
| Type *Ty0 = Op0->getType(); |
| Value *Op1 = I->getOperand(0); |
| Type *Ty1 = Op1->getType(); |
| if (Ty0 == Ty1) { |
| if (InstOpcode == Opcode || InstOpcode == AltOpcode) |
| continue; |
| if (Opcode == AltOpcode) { |
| assert(isValidForAlternation(Opcode) && |
| isValidForAlternation(InstOpcode) && |
| "Cast isn't safe for alternation, logic needs to be updated!"); |
| AltOpcode = InstOpcode; |
| AltOp = I; |
| continue; |
| } |
| } |
| } else if (auto *Inst = dyn_cast<CmpInst>(I); Inst && IsCmpOp) { |
| auto *BaseInst = cast<CmpInst>(MainOp); |
| Type *Ty0 = BaseInst->getOperand(0)->getType(); |
| Type *Ty1 = Inst->getOperand(0)->getType(); |
| if (Ty0 == Ty1) { |
| assert(InstOpcode == Opcode && "Expected same CmpInst opcode."); |
| assert(InstOpcode == AltOpcode && |
| "Alternate instructions are only supported by BinaryOperator " |
| "and CastInst."); |
| // Check for compatible operands. If the corresponding operands are not |
| // compatible - need to perform alternate vectorization. |
| CmpInst::Predicate CurrentPred = Inst->getPredicate(); |
| CmpInst::Predicate SwappedCurrentPred = |
| CmpInst::getSwappedPredicate(CurrentPred); |
| |
| if ((VL.size() == 2 || SwappedPredsCompatible) && |
| (BasePred == CurrentPred || BasePred == SwappedCurrentPred)) |
| continue; |
| |
| if (isCmpSameOrSwapped(BaseInst, Inst, TLI)) |
| continue; |
| if (CmpSamePredicateHelper::canConvertTo(Inst, InterchangeablePred)) |
| continue; |
| auto *AltInst = cast<CmpInst>(AltOp); |
| if (MainOp != AltOp) { |
| if (isCmpSameOrSwapped(AltInst, Inst, TLI)) |
| continue; |
| } else if (BasePred != CurrentPred) { |
| assert( |
| isValidForAlternation(InstOpcode) && |
| "CmpInst isn't safe for alternation, logic needs to be updated!"); |
| AltOp = I; |
| continue; |
| } |
| CmpInst::Predicate AltPred = AltInst->getPredicate(); |
| if (BasePred == CurrentPred || BasePred == SwappedCurrentPred || |
| AltPred == CurrentPred || AltPred == SwappedCurrentPred) |
| continue; |
| } |
| } else if (InstOpcode == Opcode) { |
| assert(InstOpcode == AltOpcode && |
| "Alternate instructions are only supported by BinaryOperator and " |
| "CastInst."); |
| if (auto *Gep = dyn_cast<GetElementPtrInst>(I)) { |
| if (Gep->getNumOperands() != 2 || |
| Gep->getOperand(0)->getType() != MainOp->getOperand(0)->getType()) |
| return InstructionsState::invalid(); |
| } else if (auto *EI = dyn_cast<ExtractElementInst>(I)) { |
| if (!isVectorLikeInstWithConstOps(EI)) |
| return InstructionsState::invalid(); |
| } else if (auto *LI = dyn_cast<LoadInst>(I)) { |
| auto *BaseLI = cast<LoadInst>(MainOp); |
| if (!LI->isSimple() || !BaseLI->isSimple()) |
| return InstructionsState::invalid(); |
| } else if (auto *Call = dyn_cast<CallInst>(I)) { |
| auto *CallBase = cast<CallInst>(MainOp); |
| Intrinsic::ID ID = getVectorIntrinsicIDForCall(Call, &TLI); |
| Intrinsic::ID Equivalent = isEquivalentIntrinsicID(ID, BaseID); |
| if (Call->getCalledFunction() != CallBase->getCalledFunction() && |
| isEquivalentIntrinsicID(Equivalent, Intrinsic::fmuladd) == |
| Intrinsic::not_intrinsic) |
| return InstructionsState::invalid(); |
| if (Call->hasOperandBundles() && |
| (!CallBase->hasOperandBundles() || |
| !std::equal(Call->op_begin() + Call->getBundleOperandsStartIndex(), |
| Call->op_begin() + Call->getBundleOperandsEndIndex(), |
| CallBase->op_begin() + |
| CallBase->getBundleOperandsStartIndex()))) |
| return InstructionsState::invalid(); |
| if (ID != BaseID && Equivalent == Intrinsic::not_intrinsic) |
| return InstructionsState::invalid(); |
| if (!ID) { |
| SmallVector<VFInfo, 4> Mappings = |
| VFDatabase(*Call).getMappings(*Call); |
| if (Mappings.size() != BaseMappings.size() || |
| Mappings.front().ISA != BaseMappings.front().ISA || |
| Mappings.front().ScalarName != BaseMappings.front().ScalarName || |
| Mappings.front().VectorName != BaseMappings.front().VectorName || |
| Mappings.front().Shape.VF != BaseMappings.front().Shape.VF || |
| Mappings.front().Shape.Parameters != |
| BaseMappings.front().Shape.Parameters) |
| return InstructionsState::invalid(); |
| } |
| } |
| continue; |
| } |
| return InstructionsState::invalid(); |
| } |
| |
| if (IsBinOp) { |
| if (!BinOpHelper.hasDefinedMainOpcode() || |
| !BinOpHelper.hasDefinedAltOpcode()) |
| return InstructionsState::invalid(); |
| MainOp = findInstructionWithOpcode(VL, BinOpHelper.getMainOpcode()); |
| assert(MainOp && "Cannot find MainOp with Opcode from BinOpHelper."); |
| AltOp = findInstructionWithOpcode(VL, BinOpHelper.getAltOpcode()); |
| assert(AltOp && "Cannot find AltOp with Opcode from BinOpHelper."); |
| } else if (auto *CB = dyn_cast<CallInst>(MainOp); |
| CB && |
| getVectorIntrinsicIDForCall(CB, &TLI) == Intrinsic::fmuladd) { |
| // fma and fmuladd share a single vector fma node; use the fma as the |
| // representative so the fused form is not weakened to fmuladd. |
| auto *It = find_if(VL, [&](Value *V) { |
| auto *CI = dyn_cast<CallInst>(V); |
| return CI && getVectorIntrinsicIDForCall(CI, &TLI) == Intrinsic::fma; |
| }); |
| if (It != VL.end()) |
| MainOp = AltOp = cast<Instruction>(*It); |
| } |
| if (IsCmpOp && InterchangeablePred != CmpInst::BAD_ICMP_PREDICATE && |
| InterchangeablePred != BasePred) { |
| // Every lane is convertible to the shared predicate, so the alternate |
| // operation is never set for such bundles. |
| auto *SharedIt = find_if(VL, [&](Value *V) { |
| auto *CI = dyn_cast<ICmpInst>(V); |
| return CI && CI->getPredicate() == InterchangeablePred; |
| }); |
| assert(SharedIt != VL.end() && |
| "Expected an instruction with the shared predicate."); |
| MainOp = AltOp = cast<Instruction>(*SharedIt); |
| } |
| assert((MainOp == AltOp || !allSameOpcode(VL)) && |
| "Incorrect implementation of allSameOpcode."); |
| InstructionsState S(MainOp, AltOp); |
| assert(all_of(VL, |
| [&](Value *V) { |
| return isa<PoisonValue>(V) || |
| S.getMatchingMainOpOrAltOp(cast<Instruction>(V)); |
| }) && |
| "Invalid InstructionsState."); |
| return S; |
| } |
| |
| std::pair<Instruction *, SmallVector<Value *>> |
| convertTo(Instruction *I, const InstructionsState &S) { |
| Instruction *SelectedOp = S.getMatchingMainOpOrAltOp(I); |
| assert(SelectedOp && "Cannot convert the instruction."); |
| if (I->isBinaryOp()) { |
| BinOpSameOpcodeHelper Converter(I); |
| return std::make_pair(SelectedOp, Converter.getOperand(SelectedOp)); |
| } |
| // Use args() to skip the trailing callee operand in CallInst::operands(). |
| if (auto *CI = dyn_cast<CallInst>(I)) |
| return std::make_pair(SelectedOp, SmallVector<Value *>(CI->args())); |
| // A comparison lane interchangeable with the main operation (e.g. x == 0 |
| // in an x <u C bundle) is emitted with the main predicate and the |
| // adjusted constant. |
| if (auto *MainCI = dyn_cast<ICmpInst>(SelectedOp); |
| MainCI && !S.isAltShuffle()) |
| if (ConstantInt *C = CmpSamePredicateHelper::getAdjustedConstant( |
| cast<ICmpInst>(I), MainCI->getPredicate())) |
| return std::make_pair(SelectedOp, |
| SmallVector<Value *>{I->getOperand(0), C}); |
| return std::make_pair(SelectedOp, SmallVector<Value *>(I->operands())); |
| } |
| |
| bool isAlternateInstruction(Instruction *I, Instruction *MainOp, |
| Instruction *AltOp, const TargetLibraryInfo &TLI) { |
| if (auto *MainCI = dyn_cast<CmpInst>(MainOp)) { |
| auto *AltCI = cast<CmpInst>(AltOp); |
| CmpInst::Predicate MainP = MainCI->getPredicate(); |
| [[maybe_unused]] CmpInst::Predicate AltP = AltCI->getPredicate(); |
| assert(MainP != AltP && "Expected different main/alternate predicates."); |
| auto *CI = cast<CmpInst>(I); |
| if (isCmpSameOrSwapped(MainCI, CI, TLI)) |
| return false; |
| if (isCmpSameOrSwapped(AltCI, CI, TLI)) |
| return true; |
| CmpInst::Predicate P = CI->getPredicate(); |
| CmpInst::Predicate SwappedP = CmpInst::getSwappedPredicate(P); |
| |
| assert((MainP == P || AltP == P || MainP == SwappedP || AltP == SwappedP) && |
| "CmpInst expected to match either main or alternate predicate or " |
| "their swap."); |
| return MainP != P && MainP != SwappedP; |
| } |
| return InstructionsState(MainOp, AltOp).getMatchingMainOpOrAltOp(I) == AltOp; |
| } |
| |
| SmallVector<SmallVector<Value *>> scanAltAssociativeOperands( |
| const InstructionsState &S, const TargetLibraryInfo &TLI, |
| ArrayRef<Value *> VL, ArrayRef<Value *> Op0, ArrayRef<Value *> Op1, |
| SmallVectorImpl<Value *> &ReassocScalars, SmallBitVector &SubLanes) { |
| assert(S.isAltShuffle() && "Expected an alternate node."); |
| const unsigned NumLanes = VL.size(); |
| SmallVector<unsigned> LaneOpcodes = |
| map_to_vector(seq<unsigned>(NumLanes), [&](unsigned Lane) { |
| return isAlternateInstruction(cast<Instruction>(VL[Lane]), |
| S.getMainOp(), S.getAltOp(), TLI) |
| ? S.getAltOpcode() |
| : S.getOpcode(); |
| }); |
| // A lane value peels only as a single-use chain link with the lane's own |
| // opcode, keeping every combine level on the same main/alt pattern. |
| auto GetChainLink = [&](unsigned Lane, Value *V) -> Instruction * { |
| auto *I = dyn_cast<Instruction>(V); |
| if (!I || !I->hasOneUse() || I->getOpcode() != LaneOpcodes[Lane] || |
| !isReassocChainLink(I)) |
| return nullptr; |
| return I; |
| }; |
| SmallVector<SmallVector<Value *>> Columns; |
| Columns.emplace_back(Op0.begin(), Op0.end()); |
| Columns.emplace_back(Op1.begin(), Op1.end()); |
| // The chain link of a commutative lane may sit in the second column; |
| // normalize so every lane's link leads. |
| for (unsigned Lane : seq<unsigned>(NumLanes)) { |
| if (GetChainLink(Lane, Columns[0][Lane])) |
| continue; |
| Instruction *Link = GetChainLink(Lane, Columns[1][Lane]); |
| if (!Link || !Link->isCommutative()) |
| return {}; |
| std::swap(Columns[0][Lane], Columns[1][Lane]); |
| } |
| // Peel the leading column while every lane stays a matching chain link. |
| while (all_of(seq<unsigned>(NumLanes), [&](unsigned Lane) { |
| return GetChainLink(Lane, Columns[0][Lane]) != nullptr; |
| })) { |
| SmallVector<Value *> NewColumn(NumLanes); |
| for (unsigned Lane : seq<unsigned>(NumLanes)) { |
| Instruction *Link = GetChainLink(Lane, Columns[0][Lane]); |
| ReassocScalars.push_back(Link); |
| // The chain of a commutative lane may continue in the second operand; |
| // keep the chain link as the running value. |
| unsigned RunningOp = Link->isCommutative() && |
| !GetChainLink(Lane, Link->getOperand(0)) && |
| GetChainLink(Lane, Link->getOperand(1)) |
| ? 1 |
| : 0; |
| NewColumn[Lane] = Link->getOperand(1 - RunningOp); |
| Columns[0][Lane] = Link->getOperand(RunningOp); |
| } |
| Columns.insert(std::next(Columns.begin()), std::move(NewColumn)); |
| } |
| assert(!ReassocScalars.empty() && |
| "Normalization guarantees at least one peeled level."); |
| SubLanes.resize(NumLanes); |
| for (unsigned Lane : seq<unsigned>(NumLanes)) |
| if (LaneOpcodes[Lane] == Instruction::Sub || |
| LaneOpcodes[Lane] == Instruction::FSub) |
| SubLanes.set(Lane); |
| return Columns; |
| } |
| } // namespace llvm::slpvectorizer |