[llvm] [SLP][AArch64]Support masked div/rem on non-pow-2 vectors (PR #210623)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 19 11:36:46 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
@llvm/pr-subscribers-backend-aarch64
Author: Alexey Bataev (alexey-bataev)
<details>
<summary>Changes</summary>
For fixed-width integer div/rem on AArch64 SVE, a non-power-of-2 vector
cannot execute as a single whole-register operation. When profitable,
pad the vector to the next full register and use the masked div/rem
intrinsics.
Fixes #<!-- -->207880
---
Full diff: https://github.com/llvm/llvm-project/pull/210623.diff
7 Files Affected:
- (modified) llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp (+12)
- (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp (+83-3)
- (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.cpp (+15)
- (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.h (+6)
- (modified) llvm/test/Analysis/CostModel/AArch64/sve-div.ll (+2-2)
- (modified) llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2-revec.ll (+4-1)
- (modified) llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2.ll (+8-2)
``````````diff
diff --git a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
index 9d448b4a8681a..df0e3ef8a6d6b 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp
@@ -12,6 +12,7 @@
#include "AArch64SMEAttributes.h"
#include "MCTargetDesc/AArch64AddressingModes.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/bit.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/CodeGen/BasicTTIImpl.h"
@@ -4832,6 +4833,17 @@ InstructionCost AArch64TTIImpl::getArithmeticInstrCost(
if (nullptr != Entry)
return Entry->Cost;
}
+ // A non-power-of-2 count can't divide as a single whole-register op
+ // (an inactive lane's leftover value could be a zero divisor and
+ // trap), so the legalizer emits one div per whole register plus one
+ // per set bit of the remainder (e.g. <7 x i32> emits 3 divs, not 2).
+ if (auto *FVTy = dyn_cast<FixedVectorType>(Ty);
+ FVTy && LT.second.isFixedLengthVector()) {
+ unsigned NumElts = FVTy->getNumElements();
+ unsigned RegElts = LT.second.getVectorNumElements();
+ if (RegElts > 0)
+ Cost = (NumElts / RegElts + popcount(NumElts % RegElts)) * 2;
+ }
// For 8/16-bit elements, the cost is higher because the type
// requires promotion and possibly splitting:
if (LT.second.getScalarType() == MVT::i8)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 6dde1850d58d5..dd8aa4a877fa2 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -421,6 +421,48 @@ getFloorFullVectorNumberOfElements(const TargetTransformInfo &TTI, Type *Ty,
return (Sz / RegVF) * RegVF;
}
+/// For a non-power-of-2 \p NumElts-wide integer div/rem \p Opcode, returns the
+/// padded full-register vector type if padding is structurally possible, or
+/// nullptr if the vector already fills a register or the opcode is not
+/// div/rem. Does not check profitability; see getMaskedDivRemCost for that.
+static FixedVectorType *getMaskedDivRemType(const TargetTransformInfo &TTI,
+ unsigned Opcode, Type *ScalarTy,
+ unsigned NumElts) {
+ if (!Instruction::isIntDivRem(Opcode) || has_single_bit(NumElts))
+ return nullptr;
+ unsigned PaddedNumElts =
+ getFullVectorNumberOfElements(TTI, ScalarTy, NumElts);
+ if (PaddedNumElts == NumElts)
+ return nullptr;
+ return cast<FixedVectorType>(getWidenedType(ScalarTy, PaddedNumElts));
+}
+
+/// For a non-power-of-2 \p NumElts-wide integer div/rem \p Opcode, checks if
+/// padding to a full register and using the masked div/rem intrinsic is
+/// cheaper than the direct vector op. Returns the cost of the masked
+/// alternative, or an invalid cost if it is not applicable or not cheaper.
+static InstructionCost getMaskedDivRemCost(const TargetTransformInfo &TTI,
+ unsigned Opcode, Type *ScalarTy,
+ unsigned NumElts,
+ TTI::TargetCostKind CostKind) {
+ FixedVectorType *PaddedVecTy =
+ getMaskedDivRemType(TTI, Opcode, ScalarTy, NumElts);
+ if (!PaddedVecTy)
+ return InstructionCost::getInvalid();
+ // One mask bit per element of the padded vector, not per padded lane.
+ auto *MaskTy =
+ FixedVectorType::get(IntegerType::getInt1Ty(ScalarTy->getContext()),
+ PaddedVecTy->getNumElements());
+ InstructionCost DirectCost = TTI.getArithmeticInstrCost(
+ Opcode, getWidenedType(ScalarTy, NumElts), CostKind);
+ IntrinsicCostAttributes ICA(getMaskedDivRemIntrinsic(Opcode), PaddedVecTy,
+ {PaddedVecTy, PaddedVecTy, MaskTy});
+ InstructionCost MaskedCost = TTI.getIntrinsicInstrCost(ICA, CostKind);
+ if (!MaskedCost.isValid() || MaskedCost >= DirectCost)
+ return InstructionCost::getInvalid();
+ return MaskedCost;
+}
+
/// Checks if the vector of instructions can be represented as a shuffle, like:
/// %x0 = extractelement <4 x i8> %x, i32 0
/// %x3 = extractelement <4 x i8> %x, i32 3
@@ -17546,6 +17588,14 @@ BoUpSLP::getEntryCost(const TreeEntry *E, ArrayRef<Value *> VectorizedVals,
return CommonCost;
}
}
+ // Masked path ignores Op1Info/Op2Info like its codegen does; keep it
+ // out of the operand-aware cost comparison below.
+ if (!E->isAltShuffle()) {
+ if (InstructionCost MaskedCost = getMaskedDivRemCost(
+ *TTI, ShuffleOrOp, ScalarTy, VL.size(), CostKind);
+ MaskedCost.isValid())
+ return MaskedCost + CommonCost;
+ }
unsigned OpIdx = isa<UnaryOperator>(VL0) ? 0 : 1;
TTI::OperandValueInfo Op1Info = getOperandInfo(E->getOperand(0));
TTI::OperandValueInfo Op2Info = getOperandInfo(E->getOperand(OpIdx));
@@ -23737,9 +23787,39 @@ Value *BoUpSLP::vectorizeTree(TreeEntry *E) {
RHS = Builder.CreateIntCast(RHS, VecTy, GetOperandSignedness(1));
}
- Value *V = Builder.CreateBinOp(
- static_cast<Instruction::BinaryOps>(E->getOpcode()), LHS,
- RHS);
+ // getMaskedDivRemType alone is not enough here: it only checks that
+ // padding is structurally possible, not that it is cheaper, so this
+ // must re-check cost to match getEntryCost's decision.
+ Value *V = nullptr;
+ if (!E->isAltShuffle()) {
+ unsigned NumElts = E->Scalars.size();
+ if (getMaskedDivRemCost(*TTI, ShuffleOrOp, ScalarTy, NumElts,
+ TTI::TCK_RecipThroughput)
+ .isValid()) {
+ FixedVectorType *PaddedVecTy =
+ getMaskedDivRemType(*TTI, ShuffleOrOp, ScalarTy, NumElts);
+ // Scale the lane count up to elements for REVEC, where each lane
+ // is itself a vector.
+ unsigned NumActiveElts = NumElts * getNumElements(ScalarTy);
+ Value *WidenedLHS = createInsertVector(
+ Builder, PoisonValue::get(PaddedVecTy), LHS, 0);
+ Value *WidenedRHS = createInsertVector(
+ Builder, PoisonValue::get(PaddedVecTy), RHS, 0);
+ SmallVector<Constant *> MaskValues(
+ PaddedVecTy->getNumElements(),
+ ConstantInt::getFalse(Builder.getContext()));
+ std::fill_n(MaskValues.begin(), NumActiveElts,
+ ConstantInt::getTrue(Builder.getContext()));
+ Value *DivRemMask = ConstantVector::get(MaskValues);
+ Value *MaskedV = Builder.CreateIntrinsic(
+ getMaskedDivRemIntrinsic(ShuffleOrOp), {PaddedVecTy},
+ {WidenedLHS, WidenedRHS, DivRemMask});
+ V = createExtractVector(Builder, MaskedV, NumActiveElts, 0);
+ }
+ }
+ if (!V)
+ V = Builder.CreateBinOp(
+ static_cast<Instruction::BinaryOps>(E->getOpcode()), LHS, RHS);
V = PropagateIRFlags(V);
V = FinalShuffle(V, E);
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.cpp
index 93239b7cd2725..048559624b6ed 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.cpp
@@ -611,4 +611,19 @@ SmallVector<Constant *> replicateMask(ArrayRef<Constant *> Val, unsigned VF) {
return NewVal;
}
+Intrinsic::ID getMaskedDivRemIntrinsic(unsigned Opcode) {
+ switch (Opcode) {
+ case Instruction::UDiv:
+ return Intrinsic::masked_udiv;
+ case Instruction::SDiv:
+ return Intrinsic::masked_sdiv;
+ case Instruction::URem:
+ return Intrinsic::masked_urem;
+ case Instruction::SRem:
+ return Intrinsic::masked_srem;
+ default:
+ llvm_unreachable("Unexpected opcode");
+ }
+}
+
} // namespace llvm::slpvectorizer
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.h b/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.h
index 51181b99078c5..d46e7b272557b 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.h
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer/SLPUtils.h
@@ -20,6 +20,7 @@
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Analysis/MemoryLocation.h"
+#include "llvm/IR/Intrinsics.h"
#include <optional>
#include <string>
@@ -273,6 +274,11 @@ SmallBitVector getAltInstrMask(ArrayRef<Value *> VL, Type *ScalarTy,
/// Replicates the given \p Val \p VF times.
SmallVector<Constant *> replicateMask(ArrayRef<Constant *> Val, unsigned VF);
+/// \returns the masked division/remainder intrinsic corresponding to \p
+/// Opcode. Disabled lanes of these intrinsics are poison rather than UB,
+/// unlike the plain opcode.
+Intrinsic::ID getMaskedDivRemIntrinsic(unsigned Opcode);
+
} // namespace llvm::slpvectorizer
#endif // LLVM_LIB_TRANSFORMS_VECTORIZE_SLPVECTORIZER_SLPUTILS_H
diff --git a/llvm/test/Analysis/CostModel/AArch64/sve-div.ll b/llvm/test/Analysis/CostModel/AArch64/sve-div.ll
index 84a9ca0fab6d8..c6494ab85a7e9 100644
--- a/llvm/test/Analysis/CostModel/AArch64/sve-div.ll
+++ b/llvm/test/Analysis/CostModel/AArch64/sve-div.ll
@@ -20,7 +20,7 @@ define void @sdiv() {
; CHECK-NEXT: Cost Model: Found costs of RThru:32 CodeSize:4 Lat:4 SizeLat:4 for: %V32i16 = sdiv <32 x i16> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:5 CodeSize:4 Lat:4 SizeLat:4 for: %V2i8 = sdiv <2 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:8 CodeSize:4 Lat:4 SizeLat:4 for: %V4i8 = sdiv <4 x i8> undef, undef
-; CHECK-NEXT: Cost Model: Found costs of RThru:16 CodeSize:4 Lat:4 SizeLat:4 for: %V6i8 = sdiv <6 x i8> undef, undef
+; CHECK-NEXT: Cost Model: Found costs of RThru:32 CodeSize:4 Lat:4 SizeLat:4 for: %V6i8 = sdiv <6 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:8 CodeSize:4 Lat:4 SizeLat:4 for: %V8i8 = sdiv <8 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:16 CodeSize:4 Lat:4 SizeLat:4 for: %V16i8 = sdiv <16 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:32 CodeSize:4 Lat:4 SizeLat:4 for: %V32i8 = sdiv <32 x i8> undef, undef
@@ -103,7 +103,7 @@ define void @udiv() {
; CHECK-NEXT: Cost Model: Found costs of RThru:32 CodeSize:4 Lat:4 SizeLat:4 for: %V32i16 = udiv <32 x i16> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:5 CodeSize:4 Lat:4 SizeLat:4 for: %V2i8 = udiv <2 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:8 CodeSize:4 Lat:4 SizeLat:4 for: %V4i8 = udiv <4 x i8> undef, undef
-; CHECK-NEXT: Cost Model: Found costs of RThru:16 CodeSize:4 Lat:4 SizeLat:4 for: %V6i8 = udiv <6 x i8> undef, undef
+; CHECK-NEXT: Cost Model: Found costs of RThru:32 CodeSize:4 Lat:4 SizeLat:4 for: %V6i8 = udiv <6 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:8 CodeSize:4 Lat:4 SizeLat:4 for: %V8i8 = udiv <8 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:16 CodeSize:4 Lat:4 SizeLat:4 for: %V16i8 = udiv <16 x i8> undef, undef
; CHECK-NEXT: Cost Model: Found costs of RThru:32 CodeSize:4 Lat:4 SizeLat:4 for: %V32i8 = udiv <32 x i8> undef, undef
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2-revec.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2-revec.ll
index ee831842f825f..44b58bc4fe977 100644
--- a/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2-revec.ll
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2-revec.ll
@@ -7,7 +7,10 @@ define void @udiv_v3v2i32(ptr noalias %dst, ptr noalias %x, ptr noalias %y) vsca
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[TMP0:%.*]] = load <6 x i32>, ptr [[X]], align 8
; CHECK-NEXT: [[TMP1:%.*]] = load <6 x i32>, ptr [[Y]], align 8
-; CHECK-NEXT: [[TMP2:%.*]] = udiv <6 x i32> [[TMP0]], [[TMP1]]
+; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <6 x i32> [[TMP0]], <6 x i32> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 poison, i32 poison>
+; CHECK-NEXT: [[TMP3:%.*]] = shufflevector <6 x i32> [[TMP1]], <6 x i32> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 poison, i32 poison>
+; CHECK-NEXT: [[TMP4:%.*]] = call <8 x i32> @llvm.masked.udiv.v8i32(<8 x i32> [[TMP5]], <8 x i32> [[TMP3]], <8 x i1> <i1 true, i1 true, i1 true, i1 true, i1 true, i1 true, i1 false, i1 false>)
+; CHECK-NEXT: [[TMP2:%.*]] = shufflevector <8 x i32> [[TMP4]], <8 x i32> poison, <6 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5>
; CHECK-NEXT: store <6 x i32> [[TMP2]], ptr [[DST]], align 8
; CHECK-NEXT: ret void
;
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2.ll
index 054f0799906cb..d7f61902c6753 100644
--- a/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2.ll
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/masked-div-rem-non-pow2.ll
@@ -7,7 +7,10 @@ define void @udiv_v7i32(ptr noalias %dst, ptr noalias %x, ptr noalias %y) vscale
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[TMP1:%.*]] = load <7 x i32>, ptr [[X]], align 4
; CHECK-NEXT: [[TMP2:%.*]] = load <7 x i32>, ptr [[Y]], align 4
-; CHECK-NEXT: [[TMP6:%.*]] = udiv <7 x i32> [[TMP1]], [[TMP2]]
+; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <7 x i32> [[TMP1]], <7 x i32> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 poison>
+; CHECK-NEXT: [[TMP3:%.*]] = shufflevector <7 x i32> [[TMP2]], <7 x i32> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 poison>
+; CHECK-NEXT: [[TMP4:%.*]] = call <8 x i32> @llvm.masked.udiv.v8i32(<8 x i32> [[TMP5]], <8 x i32> [[TMP3]], <8 x i1> <i1 true, i1 true, i1 true, i1 true, i1 true, i1 true, i1 true, i1 false>)
+; CHECK-NEXT: [[TMP6:%.*]] = shufflevector <8 x i32> [[TMP4]], <8 x i32> poison, <7 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6>
; CHECK-NEXT: store <7 x i32> [[TMP6]], ptr [[DST]], align 4
; CHECK-NEXT: ret void
;
@@ -67,7 +70,10 @@ define void @sdiv_v7i32(ptr noalias %dst, ptr noalias %x, ptr noalias %y) vscale
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[TMP1:%.*]] = load <7 x i32>, ptr [[X]], align 4
; CHECK-NEXT: [[TMP2:%.*]] = load <7 x i32>, ptr [[Y]], align 4
-; CHECK-NEXT: [[TMP6:%.*]] = sdiv <7 x i32> [[TMP1]], [[TMP2]]
+; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <7 x i32> [[TMP1]], <7 x i32> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 poison>
+; CHECK-NEXT: [[TMP3:%.*]] = shufflevector <7 x i32> [[TMP2]], <7 x i32> poison, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 poison>
+; CHECK-NEXT: [[TMP4:%.*]] = call <8 x i32> @llvm.masked.sdiv.v8i32(<8 x i32> [[TMP5]], <8 x i32> [[TMP3]], <8 x i1> <i1 true, i1 true, i1 true, i1 true, i1 true, i1 true, i1 true, i1 false>)
+; CHECK-NEXT: [[TMP6:%.*]] = shufflevector <8 x i32> [[TMP4]], <8 x i32> poison, <7 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6>
; CHECK-NEXT: store <7 x i32> [[TMP6]], ptr [[DST]], align 4
; CHECK-NEXT: ret void
;
``````````
</details>
https://github.com/llvm/llvm-project/pull/210623
More information about the llvm-commits
mailing list