[llvm] [SLPVectorizer] Widen constant strided loads. (PR #162324)
Mikhail Gudim via llvm-commits
llvm-commits at lists.llvm.org
Mon Oct 27 11:53:07 PDT 2025
https://github.com/mgudim updated https://github.com/llvm/llvm-project/pull/162324
>From 04affb69393dd05f960bd59df06a8349fc8df7fb Mon Sep 17 00:00:00 2001
From: Mikhail Gudim <mgudim at ventanamicro.com>
Date: Tue, 23 Sep 2025 15:41:57 -0700
Subject: [PATCH 1/2] [SLPVectorizer] Widen constant strided loads.
Given a set of pointers, check if they can be rearranged as follows (%s is a constant):
%b + 0 * %s + 0
%b + 0 * %s + 1
%b + 0 * %s + 2
...
%b + 0 * %s + w
%b + 1 * %s + 0
%b + 1 * %s + 1
%b + 1 * %s + 2
...
%b + 1 * %s + w
...
If the pointers can be rearanged in the above pattern, it means that the
memory can be accessed with a strided loads of width `w` and stride `%s`.
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 127 ++++++++++++++----
.../RISCV/basic-strided-loads.ll | 18 +--
2 files changed, 105 insertions(+), 40 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index cdb9e7e381121..e232ce664b2ae 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -2248,7 +2248,6 @@ class BoUpSLP {
/// Return true if an array of scalar loads can be replaced with a strided
/// load (with constant stride).
///
- /// TODO:
/// It is possible that the load gets "widened". Suppose that originally each
/// load loads `k` bytes and `PointerOps` can be arranged as follows (`%s` is
/// constant): %b + 0 * %s + 0 %b + 0 * %s + 1 %b + 0 * %s + 2
@@ -6938,36 +6937,104 @@ bool BoUpSLP::isStridedLoad(ArrayRef<Value *> PointerOps, Type *ScalarTy,
}
bool BoUpSLP::analyzeConstantStrideCandidate(
- const ArrayRef<Value *> PointerOps, Type *ScalarTy, Align Alignment,
+ const ArrayRef<Value *> PointerOps, Type *ElemTy, Align Alignment,
const SmallVectorImpl<unsigned> &SortedIndices, const int64_t Diff,
Value *Ptr0, Value *PtrN, StridedPtrInfo &SPtrInfo) const {
const size_t Sz = PointerOps.size();
- if (!isStridedLoad(PointerOps, ScalarTy, Alignment, Diff, Sz))
- return false;
+ SmallVector<int64_t> SortedOffsetsFromBase(Sz);
+ // Go through `PointerOps` in sorted order and record offsets from `Ptr0`.
+ for (unsigned I : seq<unsigned>(Sz)) {
+ Value *Ptr =
+ SortedIndices.empty() ? PointerOps[I] : PointerOps[SortedIndices[I]];
+ SortedOffsetsFromBase[I] =
+ *getPointersDiff(ElemTy, Ptr0, ElemTy, Ptr, *DL, *SE);
+ }
+
+ // The code below checks that `SortedOffsetsFromBase` looks as follows:
+ // ```
+ // [
+ // (e_{0, 0}, e_{0, 1}, ..., e_{0, GroupSize - 1}), // first group
+ // (e_{1, 0}, e_{1, 1}, ..., e_{1, GroupSize - 1}), // secon group
+ // ...
+ // (e_{NumGroups - 1, 0}, e_{NumGroups - 1, 1}, ..., e_{NumGroups - 1,
+ // GroupSize - 1}), // last group
+ // ]
+ // ```
+ // The distance between consecutive elements within each group should all be
+ // the same `StrideWithinGroup`. The distance between the first elements of
+ // consecutive groups should all be the same `StrideBetweenGroups`.
+
+ int64_t StrideWithinGroup =
+ SortedOffsetsFromBase[1] - SortedOffsetsFromBase[0];
+ // Determine size of the first group. Later we will check that all other
+ // groups have the same size.
+ unsigned GroupSize = 1;
+ for (; GroupSize != SortedOffsetsFromBase.size(); ++GroupSize) {
+ if (SortedOffsetsFromBase[GroupSize] -
+ SortedOffsetsFromBase[GroupSize - 1] !=
+ StrideWithinGroup)
+ break;
+ }
+ unsigned VecSz = Sz;
+ Type *ScalarTy = ElemTy;
+ int64_t StrideIntVal = StrideWithinGroup;
+ FixedVectorType *StridedLoadTy = getWidenedType(ScalarTy, VecSz);
- int64_t Stride = Diff / static_cast<int64_t>(Sz - 1);
+ // Quick detour: at this point we can say what the type of strided load would
+ // be if all the checks pass. Check if this type is legal for the target.
+ bool NeedsWidening = Sz != GroupSize;
+ if (NeedsWidening) {
+ if (Sz % GroupSize != 0)
+ return false;
+ VecSz = Sz / GroupSize;
- // Iterate through all pointers and check if all distances are
- // unique multiple of Dist.
- SmallSet<int64_t, 4> Dists;
- for (Value *Ptr : PointerOps) {
- int64_t Dist = 0;
- if (Ptr == PtrN)
- Dist = Diff;
- else if (Ptr != Ptr0)
- Dist = *getPointersDiff(ScalarTy, Ptr0, ScalarTy, Ptr, *DL, *SE);
- // If the strides are not the same or repeated, we can't
- // vectorize.
- if (((Dist / Stride) * Stride) != Dist || !Dists.insert(Dist).second)
- break;
+ if (StrideWithinGroup != 1)
+ return false;
+ unsigned VecSz = Sz / GroupSize;
+ ScalarTy = Type::getIntNTy(SE->getContext(),
+ DL->getTypeSizeInBits(ElemTy).getFixedValue() *
+ GroupSize);
+ StridedLoadTy = getWidenedType(ScalarTy, VecSz);
}
- if (Dists.size() == Sz) {
- Type *StrideTy = DL->getIndexType(Ptr0->getType());
- SPtrInfo.StrideVal = ConstantInt::get(StrideTy, Stride);
- SPtrInfo.Ty = getWidenedType(ScalarTy, Sz);
- return true;
+
+ if (!isStridedLoad(PointerOps, ScalarTy, Alignment, Diff, VecSz))
+ return false;
+
+ if (NeedsWidening) {
+ // Continue with checking the "shape" of `SortedOffsetsFromBase`.
+ // Check that the strides between groups are all the same.
+ unsigned CurrentGroupStartIdx = GroupSize;
+ int64_t StrideBetweenGroups =
+ SortedOffsetsFromBase[GroupSize] - SortedOffsetsFromBase[0];
+ StrideIntVal = StrideBetweenGroups;
+ for (; CurrentGroupStartIdx < Sz; CurrentGroupStartIdx += GroupSize) {
+ if (SortedOffsetsFromBase[CurrentGroupStartIdx] -
+ SortedOffsetsFromBase[CurrentGroupStartIdx - GroupSize] !=
+ StrideBetweenGroups)
+ return false;
+ }
+
+ auto CheckGroup = [&](const unsigned StartIdx, const unsigned GroupSize0,
+ const int64_t StrideWithinGroup) -> bool {
+ unsigned GroupEndIdx = StartIdx + 1;
+ for (; GroupEndIdx != Sz; ++GroupEndIdx) {
+ if (SortedOffsetsFromBase[GroupEndIdx] -
+ SortedOffsetsFromBase[GroupEndIdx - 1] !=
+ StrideWithinGroup)
+ break;
+ }
+ return GroupEndIdx - StartIdx == GroupSize0;
+ };
+ for (unsigned I = 0; I < Sz; I += GroupSize) {
+ if (!CheckGroup(I, GroupSize, StrideWithinGroup))
+ return false;
+ }
}
- return false;
+
+ Type *StrideTy = DL->getIndexType(Ptr0->getType());
+ SPtrInfo.StrideVal = ConstantInt::get(StrideTy, StrideIntVal);
+ SPtrInfo.Ty = StridedLoadTy;
+ return true;
}
bool BoUpSLP::analyzeRtStrideCandidate(ArrayRef<Value *> PointerOps,
@@ -14989,11 +15056,19 @@ BoUpSLP::getEntryCost(const TreeEntry *E, ArrayRef<Value *> VectorizedVals,
}
break;
case TreeEntry::StridedVectorize: {
+ const StridedPtrInfo &SPtrInfo = TreeEntryToStridedPtrInfoMap.at(E);
+ FixedVectorType *StridedLoadTy = SPtrInfo.Ty;
+ assert(StridedLoadTy && "Missing StridedPoinerInfo for tree entry.");
Align CommonAlignment =
computeCommonAlignment<LoadInst>(UniqueValues.getArrayRef());
VecLdCost = TTI->getStridedMemoryOpCost(
- Instruction::Load, VecTy, LI0->getPointerOperand(),
+ Instruction::Load, StridedLoadTy, LI0->getPointerOperand(),
/*VariableMask=*/false, CommonAlignment, CostKind);
+ if (StridedLoadTy != VecTy)
+ VecLdCost +=
+ TTI->getCastInstrCost(Instruction::BitCast, VecTy, StridedLoadTy,
+ getCastContextHint(*E), CostKind);
+
break;
}
case TreeEntry::CompressVectorize: {
@@ -19760,6 +19835,8 @@ Value *BoUpSLP::vectorizeTree(TreeEntry *E) {
? NewLI
: ::propagateMetadata(NewLI, E->Scalars);
+ if (StridedLoadTy != VecTy)
+ V = Builder.CreateBitOrPointerCast(V, VecTy);
V = FinalShuffle(V, E);
E->VectorizedValue = V;
++NumVectorInstructions;
diff --git a/llvm/test/Transforms/SLPVectorizer/RISCV/basic-strided-loads.ll b/llvm/test/Transforms/SLPVectorizer/RISCV/basic-strided-loads.ll
index f8229b3555653..f3f9191a6fdc7 100644
--- a/llvm/test/Transforms/SLPVectorizer/RISCV/basic-strided-loads.ll
+++ b/llvm/test/Transforms/SLPVectorizer/RISCV/basic-strided-loads.ll
@@ -1,6 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -mtriple=riscv64 -mattr=+m,+v -passes=slp-vectorizer -S < %s | FileCheck %s
+; RUN: opt -mtriple=riscv64 -mattr=+m,+v,+unaligned-vector-mem -passes=slp-vectorizer -S < %s | FileCheck %s
define void @const_stride_1_no_reordering(ptr %pl, ptr %ps) {
; CHECK-LABEL: define void @const_stride_1_no_reordering(
@@ -621,21 +621,9 @@ define void @constant_stride_widen_no_reordering(ptr %pl, i64 %stride, ptr %ps)
; CHECK-LABEL: define void @constant_stride_widen_no_reordering(
; CHECK-SAME: ptr [[PL:%.*]], i64 [[STRIDE:%.*]], ptr [[PS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[GEP_L0:%.*]] = getelementptr inbounds i8, ptr [[PL]], i64 0
-; CHECK-NEXT: [[GEP_L4:%.*]] = getelementptr inbounds i8, ptr [[PL]], i64 100
-; CHECK-NEXT: [[GEP_L8:%.*]] = getelementptr inbounds i8, ptr [[PL]], i64 200
-; CHECK-NEXT: [[GEP_L12:%.*]] = getelementptr inbounds i8, ptr [[PL]], i64 300
; CHECK-NEXT: [[GEP_S0:%.*]] = getelementptr inbounds i8, ptr [[PS]], i64 0
-; CHECK-NEXT: [[TMP1:%.*]] = load <4 x i8>, ptr [[GEP_L0]], align 1
-; CHECK-NEXT: [[TMP2:%.*]] = load <4 x i8>, ptr [[GEP_L4]], align 1
-; CHECK-NEXT: [[TMP3:%.*]] = load <4 x i8>, ptr [[GEP_L8]], align 1
-; CHECK-NEXT: [[TMP4:%.*]] = load <4 x i8>, ptr [[GEP_L12]], align 1
-; CHECK-NEXT: [[TMP5:%.*]] = shufflevector <4 x i8> [[TMP1]], <4 x i8> poison, <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
-; CHECK-NEXT: [[TMP6:%.*]] = shufflevector <4 x i8> [[TMP2]], <4 x i8> poison, <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
-; CHECK-NEXT: [[TMP7:%.*]] = shufflevector <4 x i8> [[TMP1]], <4 x i8> [[TMP2]], <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
-; CHECK-NEXT: [[TMP11:%.*]] = shufflevector <4 x i8> [[TMP3]], <4 x i8> poison, <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
-; CHECK-NEXT: [[TMP9:%.*]] = shufflevector <16 x i8> [[TMP7]], <16 x i8> [[TMP11]], <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 16, i32 17, i32 18, i32 19, i32 poison, i32 poison, i32 poison, i32 poison>
-; CHECK-NEXT: [[TMP10:%.*]] = shufflevector <4 x i8> [[TMP4]], <4 x i8> poison, <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison, i32 poison>
-; CHECK-NEXT: [[TMP8:%.*]] = shufflevector <16 x i8> [[TMP9]], <16 x i8> [[TMP10]], <16 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9, i32 10, i32 11, i32 16, i32 17, i32 18, i32 19>
+; CHECK-NEXT: [[TMP1:%.*]] = call <4 x i32> @llvm.experimental.vp.strided.load.v4i32.p0.i64(ptr align 1 [[GEP_L0]], i64 100, <4 x i1> splat (i1 true), i32 4)
+; CHECK-NEXT: [[TMP8:%.*]] = bitcast <4 x i32> [[TMP1]] to <16 x i8>
; CHECK-NEXT: store <16 x i8> [[TMP8]], ptr [[GEP_S0]], align 1
; CHECK-NEXT: ret void
;
>From 2b61c6703a2e2ff4140e44724a93d479821b91df Mon Sep 17 00:00:00 2001
From: Mikhail Gudim <mgudim at ventanamicro.com>
Date: Mon, 27 Oct 2025 11:21:42 -0700
Subject: [PATCH 2/2] addressed review comments.
---
.../Transforms/Vectorize/SLPVectorizer.cpp | 31 +++++++++----------
1 file changed, 15 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index e232ce664b2ae..2883a29099b8b 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -6937,7 +6937,7 @@ bool BoUpSLP::isStridedLoad(ArrayRef<Value *> PointerOps, Type *ScalarTy,
}
bool BoUpSLP::analyzeConstantStrideCandidate(
- const ArrayRef<Value *> PointerOps, Type *ElemTy, Align Alignment,
+ const ArrayRef<Value *> PointerOps, Type *ScalarTy, Align Alignment,
const SmallVectorImpl<unsigned> &SortedIndices, const int64_t Diff,
Value *Ptr0, Value *PtrN, StridedPtrInfo &SPtrInfo) const {
const size_t Sz = PointerOps.size();
@@ -6947,7 +6947,7 @@ bool BoUpSLP::analyzeConstantStrideCandidate(
Value *Ptr =
SortedIndices.empty() ? PointerOps[I] : PointerOps[SortedIndices[I]];
SortedOffsetsFromBase[I] =
- *getPointersDiff(ElemTy, Ptr0, ElemTy, Ptr, *DL, *SE);
+ *getPointersDiff(ScalarTy, Ptr0, ScalarTy, Ptr, *DL, *SE);
}
// The code below checks that `SortedOffsetsFromBase` looks as follows:
@@ -6968,17 +6968,16 @@ bool BoUpSLP::analyzeConstantStrideCandidate(
SortedOffsetsFromBase[1] - SortedOffsetsFromBase[0];
// Determine size of the first group. Later we will check that all other
// groups have the same size.
- unsigned GroupSize = 1;
- for (; GroupSize != SortedOffsetsFromBase.size(); ++GroupSize) {
- if (SortedOffsetsFromBase[GroupSize] -
- SortedOffsetsFromBase[GroupSize - 1] !=
- StrideWithinGroup)
- break;
- }
+ auto isEndOfGroupIndex = [=, &SortedOffsetsFromBase](unsigned Idx) {
+ return SortedOffsetsFromBase[Idx] - SortedOffsetsFromBase[Idx - 1] !=
+ StrideWithinGroup;
+ };
+ unsigned GroupSize = *llvm::find_if(seq<unsigned>(1, Sz), isEndOfGroupIndex);
+
unsigned VecSz = Sz;
- Type *ScalarTy = ElemTy;
+ Type *NewScalarTy = ScalarTy;
int64_t StrideIntVal = StrideWithinGroup;
- FixedVectorType *StridedLoadTy = getWidenedType(ScalarTy, VecSz);
+ FixedVectorType *StridedLoadTy = getWidenedType(NewScalarTy, VecSz);
// Quick detour: at this point we can say what the type of strided load would
// be if all the checks pass. Check if this type is legal for the target.
@@ -6991,13 +6990,13 @@ bool BoUpSLP::analyzeConstantStrideCandidate(
if (StrideWithinGroup != 1)
return false;
unsigned VecSz = Sz / GroupSize;
- ScalarTy = Type::getIntNTy(SE->getContext(),
- DL->getTypeSizeInBits(ElemTy).getFixedValue() *
- GroupSize);
- StridedLoadTy = getWidenedType(ScalarTy, VecSz);
+ NewScalarTy = Type::getIntNTy(
+ SE->getContext(),
+ DL->getTypeSizeInBits(ScalarTy).getFixedValue() * GroupSize);
+ StridedLoadTy = getWidenedType(NewScalarTy, VecSz);
}
- if (!isStridedLoad(PointerOps, ScalarTy, Alignment, Diff, VecSz))
+ if (!isStridedLoad(PointerOps, NewScalarTy, Alignment, Diff, VecSz))
return false;
if (NeedsWidening) {
More information about the llvm-commits
mailing list