[llvm] [VectorCombine] Fold contiguous loads into a single vector load (PR #185736)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 2 00:21:54 PDT 2026
https://github.com/ParkHanbum updated https://github.com/llvm/llvm-project/pull/185736
>From 6f4b20cc7cd0a585525541e50fe4c17f508f048a Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Wed, 11 Mar 2026 04:17:15 +0900
Subject: [PATCH 01/14] add testcases for upcoming pathc
---
.../X86/fold-contiguous-loads.ll | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
new file mode 100644
index 0000000000000..7f9128f0d8d4f
--- /dev/null
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -0,0 +1,126 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- -mattr=SSE2 | FileCheck %s --check-prefixes=CHECK,SSE
+; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- -mattr=AVX2 | FileCheck %s --check-prefixes=CHECK,AVX
+
+; 1. Basic behavior: Extract subvector from a wide load with offset (Success case)
+define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @extract_subvector_with_offset(
+; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[TMP2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
+; CHECK-NEXT: ret <2 x float> [[TMP2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %v2
+}
+
+; 2. Basic behavior: Extract middle subvector with different type (Success case)
+define <2 x double> @extract_subvector_middle(ptr %arg0) {
+; SSE-LABEL: define <2 x double> @extract_subvector_middle(
+; SSE-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; SSE-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; SSE-NEXT: [[TMP1:%.*]] = load <3 x double>, ptr [[V0]], align 16
+; SSE-NEXT: [[V2:%.*]] = shufflevector <3 x double> [[TMP1]], <3 x double> poison, <2 x i32> <i32 1, i32 2>
+; SSE-NEXT: ret <2 x double> [[V2]]
+;
+; AVX-LABEL: define <2 x double> @extract_subvector_middle(
+; AVX-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; AVX-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; AVX-NEXT: [[V1:%.*]] = load <4 x double>, ptr [[V0]], align 16
+; AVX-NEXT: [[V2:%.*]] = shufflevector <4 x double> [[V1]], <4 x double> poison, <2 x i32> <i32 1, i32 2>
+; AVX-NEXT: ret <2 x double> [[V2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load <4 x double>, ptr %v0, align 16
+ %v2 = shufflevector <4 x double> %v1, <4 x double> poison, <2 x i32> <i32 1, i32 2>
+ ret <2 x double> %v2
+}
+
+; 3. Negative test: Load instruction has multiple uses (!hasOneUse)
+; Added a store instruction using the load result to the success case.
+define <2 x float> @negative_multi_use(ptr %arg0, ptr %out) {
+; CHECK-LABEL: define <2 x float> @negative_multi_use(
+; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[OUT:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: store <4 x float> [[V1]], ptr [[OUT]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load <4 x float>, ptr %v0, align 8
+ store <4 x float> %v1, ptr %out, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %v2
+}
+
+; 4. Negative test: Non-contiguous memory access (Reverse mask)
+; Tweaked the mask to <3, 2> from the success case.
+define <2 x float> @negative_non_contiguous(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @negative_non_contiguous(
+; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 3, i32 2>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 3, i32 2>
+ ret <2 x float> %v2
+}
+
+; 5. Negative test: Volatile load (!isSimple)
+; Added the volatile attribute to the load from the success case.
+define <2 x float> @negative_volatile_load(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @negative_volatile_load(
+; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; CHECK-NEXT: [[V1:%.*]] = load volatile <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load volatile <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %v2
+}
+
+; 6. Negative test: Different pointer bases (Base != CommonBase)
+; Kept the success case structure but mixed in a second pointer base to induce failure.
+define <2 x float> @negative_different_bases(ptr %arg0, ptr %arg1) {
+; CHECK-LABEL: define <2 x float> @negative_different_bases(
+; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[ARG1:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V0_OTHER:%.*]] = getelementptr inbounds i8, ptr [[ARG1]], i64 40
+; CHECK-NEXT: [[V1_OTHER:%.*]] = load <4 x float>, ptr [[V0_OTHER]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> [[V1_OTHER]], <2 x i32> <i32 2, i32 7>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v0_other = getelementptr inbounds i8, ptr %arg1, i64 40
+ %v1_other = load <4 x float>, ptr %v0_other, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> %v1_other, <2 x i32> <i32 2, i32 7>
+ ret <2 x float> %v2
+}
+
+; 7. Negative test: Offset continuity failure (Memory gap)
+; Tweaked the mask to <1, 3> from the success case to create a gap.
+define <2 x float> @negative_memory_gap(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @negative_memory_gap(
+; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i8, ptr %arg0, i64 40
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 1, i32 3>
+ ret <2 x float> %v2
+}
>From d757802879d5b7d964365898d40bd8b8f8ed8cb1 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Wed, 11 Mar 2026 02:14:11 +0900
Subject: [PATCH 02/14] [VectorCombine] Fold contiguous loads into a single
vector load
VectorCombine currently lacks the capability to recognize and
optimize contiguous lane extractions from wide vector loads.
The IR retains redundant wide loads and shufflevector operations
without narrowing.
(Before):
%v1 = load <4 x float>, ptr %p, align 8
%v2 = shufflevector <4 x float> %v1, poison, <2 x i32> <2, 3>
The new contiguous memory validation logic calculates the absolute
bit offset of each lane to verify strict sequential access. It
replaces the validated shuffle mask and original loads with a single
vector load combined with a constant pointer offset.
(After):
%ptr = getelementptr inbounds i8, ptr %p, i64 8
%v2 = load <2 x float>, ptr %ptr, align 8
Proof: https://alive2.llvm.org/ce/z/HduzCE
Fixed: #185556
---
.../Transforms/Vectorize/VectorCombine.cpp | 88 +++++++++++++++++++
.../X86/fold-contiguous-loads.ll | 26 +++---
2 files changed, 98 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index da8a52a6b3dca..373136ce5ce5f 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -159,6 +159,7 @@ class VectorCombine {
bool shrinkType(Instruction &I);
bool shrinkLoadForShuffles(Instruction &I);
bool shrinkPhiOfShuffles(Instruction &I);
+ bool foldContiguousLoads(Instruction &I);
void replaceValue(Instruction &Old, Value &New, bool Erase = true) {
LLVM_DEBUG(dbgs() << "VC: Replacing: " << Old << '\n');
@@ -6053,6 +6054,91 @@ bool VectorCombine::shrinkPhiOfShuffles(Instruction &I) {
return true;
}
+/// Check if a vector instruction's lanes originate from contiguous memory
+/// accesses. Fold the original loads and shuffles into a single vector load
+/// if it is profitable. For example:
+/// shufflevector(load <4 x float> ptr), poison, <2, 3>
+/// -> load <2 x float> (ptradd ptr, 8)
+/// Cost model calculations take into account the cost of the original
+/// unique load(s) and the target instruction versus the cost of the new
+/// aligned vector load.
+bool VectorCombine::foldContiguousLoads(Instruction &I) {
+ auto *VT = dyn_cast<FixedVectorType>(I.getType());
+ if (!VT || I.use_empty())
+ return false;
+
+ unsigned ElementSize = VT->getElementType()->getScalarSizeInBits();
+ unsigned NumElts = VT->getNumElements();
+ Type *EltTy = VT->getElementType();
+ Value *CommonBase = nullptr;
+ int64_t ExpectedBaseBitOffset = 0, FirstLoadOffset = 0;
+ LoadInst *FirstLI = nullptr;
+ SmallPtrSet<LoadInst *, 4> Loads;
+ for (unsigned Lane = 0; Lane < NumElts; ++Lane) {
+ InstLane IL = lookThroughShuffles(&*I.use_begin(), Lane);
+ if (!IL.first)
+ return false;
+
+ auto *LI = dyn_cast<LoadInst>(IL.first->get());
+ if (!LI)
+ return false;
+
+ if (!LI->isSimple() || !LI->hasOneUse())
+ return false;
+
+ auto *LIVTy = dyn_cast<FixedVectorType>(LI->getType());
+ if (!LIVTy || LIVTy->getElementType() != EltTy)
+ return false;
+
+ int64_t ConstantOffset = 0;
+ Value *Base = GetPointerBaseWithConstantOffset(LI->getPointerOperand(),
+ ConstantOffset, *DL);
+ int64_t AbsoluteBitOffset =
+ (ConstantOffset * 8) + (IL.second * ElementSize);
+ if (Lane == 0) {
+ if (AbsoluteBitOffset % 8 != 0)
+ return false;
+
+ CommonBase = Base;
+ ExpectedBaseBitOffset = AbsoluteBitOffset;
+ FirstLI = LI;
+ FirstLoadOffset = ConstantOffset;
+ } else {
+ if (Base != CommonBase)
+ return false;
+
+ if (AbsoluteBitOffset != ExpectedBaseBitOffset + (Lane * ElementSize))
+ return false;
+ }
+
+ Loads.insert(LI);
+ }
+
+ InstructionCost OldCost = TTI.getInstructionCost(&I, CostKind);
+ for (LoadInst *LI : Loads)
+ OldCost += TTI.getInstructionCost(LI, CostKind);
+
+ int64_t StartByteOffset = ExpectedBaseBitOffset / 8;
+ int64_t OffsetFromFirstLoad = StartByteOffset - FirstLoadOffset;
+ Align NewAlign = commonAlignment(FirstLI->getAlign(), OffsetFromFirstLoad);
+ InstructionCost NewCost =
+ TTI.getMemoryOpCost(Instruction::Load, VT, NewAlign, CostKind);
+ LLVM_DEBUG(dbgs() << "Found contiguous loads to fold: " << I
+ << "\n OldCost: " << OldCost << " vs NewCost: " << NewCost
+ << "\n");
+
+ if (OldCost < NewCost)
+ return false;
+
+ Value *NewBasePtr =
+ Builder.CreatePtrAdd(CommonBase, Builder.getInt64(StartByteOffset));
+ LoadInst *NewLoad = Builder.CreateAlignedLoad(VT, NewBasePtr, NewAlign);
+ NewLoad->copyMetadata(*FirstLI);
+ replaceValue(I, *NewLoad);
+
+ return true;
+}
+
/// This is the entry point for all transforms. Pass manager differences are
/// handled in the callers of this function.
bool VectorCombine::run() {
@@ -6153,6 +6239,8 @@ bool VectorCombine::run() {
return true;
if (foldShuffleToIdentity(I))
return true;
+ if (foldContiguousLoads(I))
+ return true;
break;
case Instruction::Load:
if (shrinkLoadForShuffles(I))
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 7f9128f0d8d4f..e513935a7887a 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -6,9 +6,8 @@
define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
; CHECK-LABEL: define <2 x float> @extract_subvector_with_offset(
; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0:[0-9]+]] {
-; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
-; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
-; CHECK-NEXT: [[TMP2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[TMP2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x float> [[TMP2]]
;
%v0 = getelementptr inbounds i8, ptr %arg0, i64 40
@@ -19,19 +18,11 @@ define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
; 2. Basic behavior: Extract middle subvector with different type (Success case)
define <2 x double> @extract_subvector_middle(ptr %arg0) {
-; SSE-LABEL: define <2 x double> @extract_subvector_middle(
-; SSE-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
-; SSE-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
-; SSE-NEXT: [[TMP1:%.*]] = load <3 x double>, ptr [[V0]], align 16
-; SSE-NEXT: [[V2:%.*]] = shufflevector <3 x double> [[TMP1]], <3 x double> poison, <2 x i32> <i32 1, i32 2>
-; SSE-NEXT: ret <2 x double> [[V2]]
-;
-; AVX-LABEL: define <2 x double> @extract_subvector_middle(
-; AVX-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
-; AVX-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
-; AVX-NEXT: [[V1:%.*]] = load <4 x double>, ptr [[V0]], align 16
-; AVX-NEXT: [[V2:%.*]] = shufflevector <4 x double> [[V1]], <4 x double> poison, <2 x i32> <i32 1, i32 2>
-; AVX-NEXT: ret <2 x double> [[V2]]
+; CHECK-LABEL: define <2 x double> @extract_subvector_middle(
+; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <2 x double> [[V2]]
;
%v0 = getelementptr inbounds i8, ptr %arg0, i64 40
%v1 = load <4 x double>, ptr %v0, align 16
@@ -124,3 +115,6 @@ define <2 x float> @negative_memory_gap(ptr %arg0) {
%v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 1, i32 3>
ret <2 x float> %v2
}
+;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+; AVX: {{.*}}
+; SSE: {{.*}}
>From 15ad22819c9e32b4e464c9e2dc852fcb26c412e4 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Thu, 12 Mar 2026 23:18:33 +0900
Subject: [PATCH 03/14] use compyMetadataForLoad for copy metadata for FirstLI
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 373136ce5ce5f..6d2e545440d35 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6133,7 +6133,7 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
Value *NewBasePtr =
Builder.CreatePtrAdd(CommonBase, Builder.getInt64(StartByteOffset));
LoadInst *NewLoad = Builder.CreateAlignedLoad(VT, NewBasePtr, NewAlign);
- NewLoad->copyMetadata(*FirstLI);
+ copyMetadataForLoad(*NewLoad, *FirstLI);
replaceValue(I, *NewLoad);
return true;
>From 52ca45d438074a778ae6ca900893fff30c5396d9 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Thu, 12 Mar 2026 23:56:43 +0900
Subject: [PATCH 04/14] Add testcasess to verify bailout on element type
mismatch
---
.../X86/fold-contiguous-loads.ll | 73 +++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index e513935a7887a..e5ae0aabb309c 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -115,6 +115,79 @@ define <2 x float> @negative_memory_gap(ptr %arg0) {
%v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 1, i32 3>
ret <2 x float> %v2
}
+
+define <4 x i32> @test_element_mismatch(ptr dereferenceable(16) align 16 %p) {
+; CHECK-LABEL: define <4 x i32> @test_element_mismatch(
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[L0:%.*]] = load <2 x i32>, ptr [[P]], align 8
+; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 8
+; CHECK-NEXT: [[L1:%.*]] = load <2 x float>, ptr [[P1]], align 8
+; CHECK-NEXT: [[V1:%.*]] = bitcast <2 x float> [[L1]] to <2 x i32>
+; CHECK-NEXT: [[RES:%.*]] = shufflevector <2 x i32> [[L0]], <2 x i32> [[V1]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: ret <4 x i32> [[RES]]
+;
+ %L0 = load <2 x i32>, ptr %p, align 8
+ %p1 = getelementptr i8, ptr %p, i64 8
+ %L1 = load <2 x float>, ptr %p1, align 8
+ %v1 = bitcast <2 x float> %L1 to <2 x i32>
+ %res = shufflevector <2 x i32> %L0, <2 x i32> %v1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i32> %res
+}
+
+define <4 x i64> @test_element_mismatch_double(ptr dereferenceable(32) align 32 %p) {
+; CHECK-LABEL: define <4 x i64> @test_element_mismatch_double(
+; CHECK-SAME: ptr align 32 dereferenceable(32) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[L0:%.*]] = load <2 x i64>, ptr [[P]], align 16
+; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 16
+; CHECK-NEXT: [[L1:%.*]] = load <2 x double>, ptr [[P1]], align 16
+; CHECK-NEXT: [[V1:%.*]] = bitcast <2 x double> [[L1]] to <2 x i64>
+; CHECK-NEXT: [[RES:%.*]] = shufflevector <2 x i64> [[L0]], <2 x i64> [[V1]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: ret <4 x i64> [[RES]]
+;
+ %L0 = load <2 x i64>, ptr %p, align 16
+ %p1 = getelementptr i8, ptr %p, i64 16
+ %L1 = load <2 x double>, ptr %p1, align 16
+ %v1 = bitcast <2 x double> %L1 to <2 x i64>
+ %res = shufflevector <2 x i64> %L0, <2 x i64> %v1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i64> %res
+}
+
+define <4 x i32> @test_element_mismatch_i16_i32(ptr dereferenceable(16) align 16 %p) {
+; CHECK-LABEL: define <4 x i32> @test_element_mismatch_i16_i32(
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[L0:%.*]] = load <4 x i16>, ptr [[P]], align 8
+; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 8
+; CHECK-NEXT: [[L1:%.*]] = load <2 x i32>, ptr [[P1]], align 8
+; CHECK-NEXT: [[V0:%.*]] = bitcast <4 x i16> [[L0]] to <2 x i32>
+; CHECK-NEXT: [[RES:%.*]] = shufflevector <2 x i32> [[V0]], <2 x i32> [[L1]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: ret <4 x i32> [[RES]]
+;
+ %L0 = load <4 x i16>, ptr %p, align 8
+ %p1 = getelementptr i8, ptr %p, i64 8
+ %L1 = load <2 x i32>, ptr %p1, align 8
+ %v0 = bitcast <4 x i16> %L0 to <2 x i32>
+ %res = shufflevector <2 x i32> %v0, <2 x i32> %L1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i32> %res
+}
+
+define <4 x i64> @test_element_mismatch_i32_i64(ptr dereferenceable(32) align 32 %p) {
+; CHECK-LABEL: define <4 x i64> @test_element_mismatch_i32_i64(
+; CHECK-SAME: ptr align 32 dereferenceable(32) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[L0:%.*]] = load <4 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 16
+; CHECK-NEXT: [[L1:%.*]] = load <2 x i64>, ptr [[P1]], align 16
+; CHECK-NEXT: [[V0:%.*]] = bitcast <4 x i32> [[L0]] to <2 x i64>
+; CHECK-NEXT: [[RES:%.*]] = shufflevector <2 x i64> [[V0]], <2 x i64> [[L1]], <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+; CHECK-NEXT: ret <4 x i64> [[RES]]
+;
+ %L0 = load <4 x i32>, ptr %p, align 16
+ %p1 = getelementptr i8, ptr %p, i64 16
+ %L1 = load <2 x i64>, ptr %p1, align 16
+ %v0 = bitcast <4 x i32> %L0 to <2 x i64>
+ %res = shufflevector <2 x i64> %v0, <2 x i64> %L1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i64> %res
+}
+
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; AVX: {{.*}}
; SSE: {{.*}}
>From 9cf6dbbcb34bccadb39adf0dcfb7f5431d7352ef Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 13 Mar 2026 00:03:06 +0900
Subject: [PATCH 05/14] add testcase which has poison its mask
---
.../VectorCombine/X86/fold-contiguous-loads.ll | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index e5ae0aabb309c..3dd3adb4559eb 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -188,6 +188,22 @@ define <4 x i64> @test_element_mismatch_i32_i64(ptr dereferenceable(32) align 32
ret <4 x i64> %res
}
+define <4 x float> @test_shuffle_poison(ptr align 16 dereferenceable(16) %p) {
+; CHECK-LABEL: define <4 x float> @test_shuffle_poison(
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[L0:%.*]] = load <2 x float>, ptr [[P]], align 4
+; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 8
+; CHECK-NEXT: [[L1:%.*]] = load <2 x float>, ptr [[P1]], align 4
+; CHECK-NEXT: [[RES:%.*]] = shufflevector <2 x float> [[L0]], <2 x float> [[L1]], <4 x i32> <i32 0, i32 poison, i32 2, i32 3>
+; CHECK-NEXT: ret <4 x float> [[RES]]
+;
+ %L0 = load <2 x float>, ptr %p, align 4
+ %p1 = getelementptr i8, ptr %p, i64 8
+ %L1 = load <2 x float>, ptr %p1, align 4
+ %res = shufflevector <2 x float> %L0, <2 x float> %L1, <4 x i32> <i32 0, i32 poison, i32 2, i32 3>
+ ret <4 x float> %res
+}
+
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; AVX: {{.*}}
; SSE: {{.*}}
>From 93a809849572580be8e4d9d35d87da53cedd948c Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 13 Mar 2026 00:34:49 +0900
Subject: [PATCH 06/14] Add testcasess for odd-sized vectors and cross-boundary
offsets in load widening
---
.../X86/fold-contiguous-loads.ll | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 3dd3adb4559eb..209f0704a31fa 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -30,6 +30,59 @@ define <2 x double> @extract_subvector_middle(ptr %arg0) {
ret <2 x double> %v2
}
+define <3 x i32> @test_odd_number_elements(ptr align 16 dereferenceable(16) %p) {
+; CHECK-LABEL: define <3 x i32> @test_odd_number_elements(
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[RES:%.*]] = load <3 x i32>, ptr [[P]], align 8
+; CHECK-NEXT: ret <3 x i32> [[RES]]
+;
+ %L0 = load <2 x i32>, ptr %p, align 8
+ %p1 = getelementptr i8, ptr %p, i64 8
+ %L1 = load <2 x i32>, ptr %p1, align 8
+ %res = shufflevector <2 x i32> %L0, <2 x i32> %L1, <3 x i32> <i32 0, i32 1, i32 2>
+ ret <3 x i32> %res
+}
+
+define <4 x i32> @test_odd_number_load_to_even_svi(ptr align 16 dereferenceable(32) %p) {
+; CHECK-LABEL: define <4 x i32> @test_odd_number_load_to_even_svi(
+; CHECK-SAME: ptr align 16 dereferenceable(32) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[RES:%.*]] = load <4 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: ret <4 x i32> [[RES]]
+;
+ %L0 = load <3 x i32>, ptr %p, align 16
+ %p1 = getelementptr i8, ptr %p, i64 12
+ %L1 = load <3 x i32>, ptr %p1, align 4
+ %res = shufflevector <3 x i32> %L0, <3 x i32> %L1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i32> %res
+}
+
+define <11 x i32> @test_odd_to_odd_large_mask(ptr align 64 dereferenceable(64) %p) {
+; CHECK-LABEL: define <11 x i32> @test_odd_to_odd_large_mask(
+; CHECK-SAME: ptr align 64 dereferenceable(64) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[RES:%.*]] = load <11 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: ret <11 x i32> [[RES]]
+;
+ %L0 = load <7 x i32>, ptr %p, align 16
+ %p1 = getelementptr i8, ptr %p, i64 28
+ %L1 = load <7 x i32>, ptr %p1, align 4
+ %res = shufflevector <7 x i32> %L0, <7 x i32> %L1, <11 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9, i32 10>
+ ret <11 x i32> %res
+}
+
+define <5 x i16> @test_load_17_mask_5_mid_offset_i16(ptr align 16 dereferenceable(68) %p) {
+; CHECK-LABEL: define <5 x i16> @test_load_17_mask_5_mid_offset_i16(
+; CHECK-SAME: ptr align 16 dereferenceable(68) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 30
+; CHECK-NEXT: [[RES:%.*]] = load <5 x i16>, ptr [[TMP1]], align 2
+; CHECK-NEXT: ret <5 x i16> [[RES]]
+;
+ %L0 = load <17 x i16>, ptr %p, align 16
+ %p1 = getelementptr i8, ptr %p, i64 34
+ %L1 = load <17 x i16>, ptr %p1, align 2
+ %res = shufflevector <17 x i16> %L0, <17 x i16> %L1, <5 x i32> <i32 15, i32 16, i32 17, i32 18, i32 19>
+ ret <5 x i16> %res
+}
+
; 3. Negative test: Load instruction has multiple uses (!hasOneUse)
; Added a store instruction using the load result to the success case.
define <2 x float> @negative_multi_use(ptr %arg0, ptr %out) {
>From 936aa60b8dfbdf79672d57f3a93d1c40d3426086 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 16:34:37 +0900
Subject: [PATCH 07/14] remove AVX check prefix
---
.../Transforms/VectorCombine/X86/fold-contiguous-loads.ll | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 209f0704a31fa..e3a6134b018c9 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -1,6 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- -mattr=SSE2 | FileCheck %s --check-prefixes=CHECK,SSE
-; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- -mattr=AVX2 | FileCheck %s --check-prefixes=CHECK,AVX
+; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- -mattr=SSE2 | FileCheck %s --check-prefixes=CHECK
; 1. Basic behavior: Extract subvector from a wide load with offset (Success case)
define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
@@ -256,7 +255,3 @@ define <4 x float> @test_shuffle_poison(ptr align 16 dereferenceable(16) %p) {
%res = shufflevector <2 x float> %L0, <2 x float> %L1, <4 x i32> <i32 0, i32 poison, i32 2, i32 3>
ret <4 x float> %res
}
-
-;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
-; AVX: {{.*}}
-; SSE: {{.*}}
>From 1f852db463233e9deea566644a5c24aefd04af2f Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 16:39:46 +0900
Subject: [PATCH 08/14] add checks for memory correctness
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 6d2e545440d35..8aa92951b26ec 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6075,11 +6075,11 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
LoadInst *FirstLI = nullptr;
SmallPtrSet<LoadInst *, 4> Loads;
for (unsigned Lane = 0; Lane < NumElts; ++Lane) {
- InstLane IL = lookThroughShuffles(&*I.use_begin(), Lane);
+ InstLane IL = lookThroughShuffles(&I, Lane);
if (!IL.first)
return false;
- auto *LI = dyn_cast<LoadInst>(IL.first->get());
+ auto *LI = dyn_cast<LoadInst>(IL.first);
if (!LI)
return false;
@@ -6090,6 +6090,13 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (!LIVTy || LIVTy->getElementType() != EltTy)
return false;
+ if (LI->getParent() != I.getParent())
+ return false;
+
+ if (isMemModifiedBetween(std::next(LI->getIterator()), I.getIterator(),
+ MemoryLocation::get(LI), AA))
+ return false;
+
int64_t ConstantOffset = 0;
Value *Base = GetPointerBaseWithConstantOffset(LI->getPointerOperand(),
ConstantOffset, *DL);
>From eed63d249a2d40b353c358f99827402ddc4edff6 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 16:40:12 +0900
Subject: [PATCH 09/14] add testcases for memory correctness
---
.../X86/fold-contiguous-loads.ll | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index e3a6134b018c9..d7a43a7b4b694 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -255,3 +255,36 @@ define <4 x float> @test_shuffle_poison(ptr align 16 dereferenceable(16) %p) {
%res = shufflevector <2 x float> %L0, <2 x float> %L1, <4 x i32> <i32 0, i32 poison, i32 2, i32 3>
ret <4 x float> %res
}
+
+define <2 x float> @negative_store_between_load_and_shuffle(ptr %p, float %x) {
+; CHECK-LABEL: define <2 x float> @negative_store_between_load_and_shuffle(
+; CHECK-SAME: ptr [[P:%.*]], float [[X:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V:%.*]] = load <4 x float>, ptr [[P]], align 4
+; CHECK-NEXT: [[Q:%.*]] = getelementptr float, ptr [[P]], i64 2
+; CHECK-NEXT: store float [[X]], ptr [[Q]], align 4
+; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x float> [[V]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
+; CHECK-NEXT: ret <2 x float> [[R]]
+;
+ %v = load <4 x float>, ptr %p, align 4
+ %q = getelementptr float, ptr %p, i64 2
+ store float %x, ptr %q, align 4
+ %r = shufflevector <4 x float> %v, <4 x float> poison,
+ <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %r
+}
+
+declare void @clobber()
+define <2 x float> @negative_call_between_load_and_shuffle(ptr %p) {
+; CHECK-LABEL: define <2 x float> @negative_call_between_load_and_shuffle(
+; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT: [[V:%.*]] = load <4 x float>, ptr [[P]], align 4
+; CHECK-NEXT: call void @clobber()
+; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x float> [[V]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
+; CHECK-NEXT: ret <2 x float> [[R]]
+;
+ %v = load <4 x float>, ptr %p, align 4
+ call void @clobber()
+ %r = shufflevector <4 x float> %v, <4 x float> poison,
+ <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %r
+}
>From eaab72923ad5aa19ea7e1f8d31c369ff80582e8f Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 17:08:19 +0900
Subject: [PATCH 10/14] minor fixes
---
.../Transforms/Vectorize/VectorCombine.cpp | 37 ++++++++++++++-----
.../X86/fold-contiguous-loads.ll | 8 ++--
2 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 8aa92951b26ec..2f674f69bdea3 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6067,9 +6067,16 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (!VT || I.use_empty())
return false;
- unsigned ElementSize = VT->getElementType()->getScalarSizeInBits();
- unsigned NumElts = VT->getNumElements();
Type *EltTy = VT->getElementType();
+ if (!DL->typeSizeEqualsStoreSize(EltTy))
+ return false;
+
+ uint64_t MaxInt64 =
+ static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
+ uint64_t ElementSizeBits = DL->getTypeStoreSizeInBits(EltTy);
+ assert((ElementSizeBits <= MaxInt64) && "element size far too large?");
+ int64_t ElementSize = static_cast<int64_t>(ElementSizeBits);
+ unsigned NumElts = VT->getNumElements();
Value *CommonBase = nullptr;
int64_t ExpectedBaseBitOffset = 0, FirstLoadOffset = 0;
LoadInst *FirstLI = nullptr;
@@ -6114,7 +6121,14 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (Base != CommonBase)
return false;
- if (AbsoluteBitOffset != ExpectedBaseBitOffset + (Lane * ElementSize))
+ uint64_t ResultLane = Lane;
+ assert(ResultLane <= MaxInt64 / static_cast<uint64_t>(ElementSize) &&
+ "result lane offset far too large?");
+
+ int64_t ExpectedBitOffset =
+ ExpectedBaseBitOffset +
+ static_cast<int64_t>(ResultLane) * ElementSize;
+ if (AbsoluteBitOffset != ExpectedBitOffset)
return false;
}
@@ -6129,20 +6143,25 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
int64_t OffsetFromFirstLoad = StartByteOffset - FirstLoadOffset;
Align NewAlign = commonAlignment(FirstLI->getAlign(), OffsetFromFirstLoad);
InstructionCost NewCost =
- TTI.getMemoryOpCost(Instruction::Load, VT, NewAlign, CostKind);
+ TTI.getMemoryOpCost(Instruction::Load, VT, NewAlign,
+ FirstLI->getPointerAddressSpace(), CostKind);
+
LLVM_DEBUG(dbgs() << "Found contiguous loads to fold: " << I
<< "\n OldCost: " << OldCost << " vs NewCost: " << NewCost
<< "\n");
- if (OldCost < NewCost)
+ if (OldCost <= NewCost)
return false;
- Value *NewBasePtr =
- Builder.CreatePtrAdd(CommonBase, Builder.getInt64(StartByteOffset));
+ Type *IndexTy = DL->getIndexType(CommonBase->getType());
+ Value *NewBasePtr = Builder.CreatePtrAdd(
+ CommonBase, ConstantInt::get(IndexTy, StartByteOffset,
+ /*isSigned=*/true));
LoadInst *NewLoad = Builder.CreateAlignedLoad(VT, NewBasePtr, NewAlign);
- copyMetadataForLoad(*NewLoad, *FirstLI);
- replaceValue(I, *NewLoad);
+ if (Loads.size() == 1)
+ copyMetadataForLoad(*NewLoad, *FirstLI);
+ replaceValue(I, *NewLoad);
return true;
}
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index d7a43a7b4b694..cb014e658ecad 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- -mattr=SSE2 | FileCheck %s --check-prefixes=CHECK
+; RUN: opt < %s -passes=vector-combine -S -mtriple=x86_64-- | FileCheck %s --check-prefixes=CHECK
; 1. Basic behavior: Extract subvector from a wide load with offset (Success case)
define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
@@ -268,8 +268,7 @@ define <2 x float> @negative_store_between_load_and_shuffle(ptr %p, float %x) {
%v = load <4 x float>, ptr %p, align 4
%q = getelementptr float, ptr %p, i64 2
store float %x, ptr %q, align 4
- %r = shufflevector <4 x float> %v, <4 x float> poison,
- <2 x i32> <i32 2, i32 3>
+ %r = shufflevector <4 x float> %v, <4 x float> poison, <2 x i32> <i32 2, i32 3>
ret <2 x float> %r
}
@@ -284,7 +283,6 @@ define <2 x float> @negative_call_between_load_and_shuffle(ptr %p) {
;
%v = load <4 x float>, ptr %p, align 4
call void @clobber()
- %r = shufflevector <4 x float> %v, <4 x float> poison,
- <2 x i32> <i32 2, i32 3>
+ %r = shufflevector <4 x float> %v, <4 x float> poison, <2 x i32> <i32 2, i32 3>
ret <2 x float> %r
}
>From d7fc4a5db4c13919d25dc7e4840c3a064fbd8452 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Tue, 2 Jun 2026 14:28:27 +0900
Subject: [PATCH 11/14] add more testcases with GEP type i32/i64
updated AliveProof: https://alive2.llvm.org/ce/z/JyMsSC
---
.../X86/fold-contiguous-loads.ll | 200 ++++++++++++++++--
1 file changed, 182 insertions(+), 18 deletions(-)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index cb014e658ecad..18c524bd01173 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -4,7 +4,7 @@
; 1. Basic behavior: Extract subvector from a wide load with offset (Success case)
define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
; CHECK-LABEL: define <2 x float> @extract_subvector_with_offset(
-; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[TMP2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x float> [[TMP2]]
@@ -15,10 +15,38 @@ define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
ret <2 x float> %v2
}
+; GEP base type i32: 10 * sizeof(i32) = 40 bytes.
+define <2 x float> @extract_subvector_with_offset_gep_i32(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @extract_subvector_with_offset_gep_i32(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[V2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i32, ptr %arg0, i64 10
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %v2
+}
+
+; GEP base type i64: 5 * sizeof(i64) = 40 bytes.
+define <2 x float> @extract_subvector_with_offset_gep_i64(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @extract_subvector_with_offset_gep_i64(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[V2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i64, ptr %arg0, i64 5
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %v2
+}
+
; 2. Basic behavior: Extract middle subvector with different type (Success case)
define <2 x double> @extract_subvector_middle(ptr %arg0) {
; CHECK-LABEL: define <2 x double> @extract_subvector_middle(
-; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x double> [[V2]]
@@ -29,9 +57,35 @@ define <2 x double> @extract_subvector_middle(ptr %arg0) {
ret <2 x double> %v2
}
+define <2 x double> @extract_subvector_middle_gep_i32(ptr %arg0) {
+; CHECK-LABEL: define <2 x double> @extract_subvector_middle_gep_i32(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <2 x double> [[V2]]
+;
+ %v0 = getelementptr inbounds i32, ptr %arg0, i64 10
+ %v1 = load <4 x double>, ptr %v0, align 16
+ %v2 = shufflevector <4 x double> %v1, <4 x double> poison, <2 x i32> <i32 1, i32 2>
+ ret <2 x double> %v2
+}
+
+define <2 x double> @extract_subvector_middle_gep_i64(ptr %arg0) {
+; CHECK-LABEL: define <2 x double> @extract_subvector_middle_gep_i64(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <2 x double> [[V2]]
+;
+ %v0 = getelementptr inbounds i64, ptr %arg0, i64 5
+ %v1 = load <4 x double>, ptr %v0, align 16
+ %v2 = shufflevector <4 x double> %v1, <4 x double> poison, <2 x i32> <i32 1, i32 2>
+ ret <2 x double> %v2
+}
+
define <3 x i32> @test_odd_number_elements(ptr align 16 dereferenceable(16) %p) {
; CHECK-LABEL: define <3 x i32> @test_odd_number_elements(
-; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
; CHECK-NEXT: [[RES:%.*]] = load <3 x i32>, ptr [[P]], align 8
; CHECK-NEXT: ret <3 x i32> [[RES]]
;
@@ -42,9 +96,35 @@ define <3 x i32> @test_odd_number_elements(ptr align 16 dereferenceable(16) %p)
ret <3 x i32> %res
}
+define <3 x i32> @test_odd_number_elements_gep_i32(ptr align 16 dereferenceable(16) %p) {
+; CHECK-LABEL: define <3 x i32> @test_odd_number_elements_gep_i32(
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
+; CHECK-NEXT: [[RES:%.*]] = load <3 x i32>, ptr [[P]], align 8
+; CHECK-NEXT: ret <3 x i32> [[RES]]
+;
+ %L0 = load <2 x i32>, ptr %p, align 8
+ %p1 = getelementptr i32, ptr %p, i64 2
+ %L1 = load <2 x i32>, ptr %p1, align 8
+ %res = shufflevector <2 x i32> %L0, <2 x i32> %L1, <3 x i32> <i32 0, i32 1, i32 2>
+ ret <3 x i32> %res
+}
+
+define <3 x i32> @test_odd_number_elements_gep_i64(ptr align 16 dereferenceable(16) %p) {
+; CHECK-LABEL: define <3 x i32> @test_odd_number_elements_gep_i64(
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
+; CHECK-NEXT: [[RES:%.*]] = load <3 x i32>, ptr [[P]], align 8
+; CHECK-NEXT: ret <3 x i32> [[RES]]
+;
+ %L0 = load <2 x i32>, ptr %p, align 8
+ %p1 = getelementptr i64, ptr %p, i64 1
+ %L1 = load <2 x i32>, ptr %p1, align 8
+ %res = shufflevector <2 x i32> %L0, <2 x i32> %L1, <3 x i32> <i32 0, i32 1, i32 2>
+ ret <3 x i32> %res
+}
+
define <4 x i32> @test_odd_number_load_to_even_svi(ptr align 16 dereferenceable(32) %p) {
; CHECK-LABEL: define <4 x i32> @test_odd_number_load_to_even_svi(
-; CHECK-SAME: ptr align 16 dereferenceable(32) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 16 dereferenceable(32) [[P:%.*]]) {
; CHECK-NEXT: [[RES:%.*]] = load <4 x i32>, ptr [[P]], align 16
; CHECK-NEXT: ret <4 x i32> [[RES]]
;
@@ -55,9 +135,36 @@ define <4 x i32> @test_odd_number_load_to_even_svi(ptr align 16 dereferenceable(
ret <4 x i32> %res
}
+define <4 x i32> @test_odd_number_load_to_even_svi_gep_i32(ptr align 16 dereferenceable(32) %p) {
+; CHECK-LABEL: define <4 x i32> @test_odd_number_load_to_even_svi_gep_i32(
+; CHECK-SAME: ptr align 16 dereferenceable(32) [[P:%.*]]) {
+; CHECK-NEXT: [[RES:%.*]] = load <4 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: ret <4 x i32> [[RES]]
+;
+ %L0 = load <3 x i32>, ptr %p, align 16
+ %p1 = getelementptr i32, ptr %p, i64 3
+ %L1 = load <3 x i32>, ptr %p1, align 4
+ %res = shufflevector <3 x i32> %L0, <3 x i32> %L1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i32> %res
+}
+
+define <4 x i32> @test_odd_number_load_to_even_svi_gep_i64(ptr align 16 dereferenceable(32) %p) {
+; CHECK-LABEL: define <4 x i32> @test_odd_number_load_to_even_svi_gep_i64(
+; CHECK-SAME: ptr align 16 dereferenceable(32) [[P:%.*]]) {
+; CHECK-NEXT: [[RES:%.*]] = load <4 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: ret <4 x i32> [[RES]]
+;
+ %L0 = load <3 x i32>, ptr %p, align 16
+ %p1.base = getelementptr i64, ptr %p, i64 1
+ %p1 = getelementptr i8, ptr %p1.base, i64 4
+ %L1 = load <3 x i32>, ptr %p1, align 4
+ %res = shufflevector <3 x i32> %L0, <3 x i32> %L1, <4 x i32> <i32 0, i32 1, i32 2, i32 3>
+ ret <4 x i32> %res
+}
+
define <11 x i32> @test_odd_to_odd_large_mask(ptr align 64 dereferenceable(64) %p) {
; CHECK-LABEL: define <11 x i32> @test_odd_to_odd_large_mask(
-; CHECK-SAME: ptr align 64 dereferenceable(64) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 64 dereferenceable(64) [[P:%.*]]) {
; CHECK-NEXT: [[RES:%.*]] = load <11 x i32>, ptr [[P]], align 16
; CHECK-NEXT: ret <11 x i32> [[RES]]
;
@@ -68,9 +175,36 @@ define <11 x i32> @test_odd_to_odd_large_mask(ptr align 64 dereferenceable(64) %
ret <11 x i32> %res
}
+define <11 x i32> @test_odd_to_odd_large_mask_gep_i32(ptr align 64 dereferenceable(64) %p) {
+; CHECK-LABEL: define <11 x i32> @test_odd_to_odd_large_mask_gep_i32(
+; CHECK-SAME: ptr align 64 dereferenceable(64) [[P:%.*]]) {
+; CHECK-NEXT: [[RES:%.*]] = load <11 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: ret <11 x i32> [[RES]]
+;
+ %L0 = load <7 x i32>, ptr %p, align 16
+ %p1 = getelementptr i32, ptr %p, i64 7
+ %L1 = load <7 x i32>, ptr %p1, align 4
+ %res = shufflevector <7 x i32> %L0, <7 x i32> %L1, <11 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9, i32 10>
+ ret <11 x i32> %res
+}
+
+define <11 x i32> @test_odd_to_odd_large_mask_gep_i64(ptr align 64 dereferenceable(64) %p) {
+; CHECK-LABEL: define <11 x i32> @test_odd_to_odd_large_mask_gep_i64(
+; CHECK-SAME: ptr align 64 dereferenceable(64) [[P:%.*]]) {
+; CHECK-NEXT: [[RES:%.*]] = load <11 x i32>, ptr [[P]], align 16
+; CHECK-NEXT: ret <11 x i32> [[RES]]
+;
+ %L0 = load <7 x i32>, ptr %p, align 16
+ %p1.base = getelementptr i64, ptr %p, i64 3
+ %p1 = getelementptr i8, ptr %p1.base, i64 4
+ %L1 = load <7 x i32>, ptr %p1, align 4
+ %res = shufflevector <7 x i32> %L0, <7 x i32> %L1, <11 x i32> <i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9, i32 10>
+ ret <11 x i32> %res
+}
+
define <5 x i16> @test_load_17_mask_5_mid_offset_i16(ptr align 16 dereferenceable(68) %p) {
; CHECK-LABEL: define <5 x i16> @test_load_17_mask_5_mid_offset_i16(
-; CHECK-SAME: ptr align 16 dereferenceable(68) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 16 dereferenceable(68) [[P:%.*]]) {
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 30
; CHECK-NEXT: [[RES:%.*]] = load <5 x i16>, ptr [[TMP1]], align 2
; CHECK-NEXT: ret <5 x i16> [[RES]]
@@ -82,11 +216,41 @@ define <5 x i16> @test_load_17_mask_5_mid_offset_i16(ptr align 16 dereferenceabl
ret <5 x i16> %res
}
+define <5 x i16> @test_load_17_mask_5_mid_offset_i16_gep_i32(ptr align 16 dereferenceable(68) %p) {
+; CHECK-LABEL: define <5 x i16> @test_load_17_mask_5_mid_offset_i16_gep_i32(
+; CHECK-SAME: ptr align 16 dereferenceable(68) [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 30
+; CHECK-NEXT: [[RES:%.*]] = load <5 x i16>, ptr [[TMP1]], align 2
+; CHECK-NEXT: ret <5 x i16> [[RES]]
+;
+ %L0 = load <17 x i16>, ptr %p, align 16
+ %p1.base = getelementptr i32, ptr %p, i64 8
+ %p1 = getelementptr i8, ptr %p1.base, i64 2
+ %L1 = load <17 x i16>, ptr %p1, align 2
+ %res = shufflevector <17 x i16> %L0, <17 x i16> %L1, <5 x i32> <i32 15, i32 16, i32 17, i32 18, i32 19>
+ ret <5 x i16> %res
+}
+
+define <5 x i16> @test_load_17_mask_5_mid_offset_i16_gep_i64(ptr align 16 dereferenceable(68) %p) {
+; CHECK-LABEL: define <5 x i16> @test_load_17_mask_5_mid_offset_i16_gep_i64(
+; CHECK-SAME: ptr align 16 dereferenceable(68) [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 30
+; CHECK-NEXT: [[RES:%.*]] = load <5 x i16>, ptr [[TMP1]], align 2
+; CHECK-NEXT: ret <5 x i16> [[RES]]
+;
+ %L0 = load <17 x i16>, ptr %p, align 16
+ %p1.base = getelementptr i64, ptr %p, i64 4
+ %p1 = getelementptr i8, ptr %p1.base, i64 2
+ %L1 = load <17 x i16>, ptr %p1, align 2
+ %res = shufflevector <17 x i16> %L0, <17 x i16> %L1, <5 x i32> <i32 15, i32 16, i32 17, i32 18, i32 19>
+ ret <5 x i16> %res
+}
+
; 3. Negative test: Load instruction has multiple uses (!hasOneUse)
; Added a store instruction using the load result to the success case.
define <2 x float> @negative_multi_use(ptr %arg0, ptr %out) {
; CHECK-LABEL: define <2 x float> @negative_multi_use(
-; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[OUT:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[OUT:%.*]]) {
; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
; CHECK-NEXT: store <4 x float> [[V1]], ptr [[OUT]], align 8
@@ -104,7 +268,7 @@ define <2 x float> @negative_multi_use(ptr %arg0, ptr %out) {
; Tweaked the mask to <3, 2> from the success case.
define <2 x float> @negative_non_contiguous(ptr %arg0) {
; CHECK-LABEL: define <2 x float> @negative_non_contiguous(
-; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 3, i32 2>
@@ -120,7 +284,7 @@ define <2 x float> @negative_non_contiguous(ptr %arg0) {
; Added the volatile attribute to the load from the success case.
define <2 x float> @negative_volatile_load(ptr %arg0) {
; CHECK-LABEL: define <2 x float> @negative_volatile_load(
-; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
; CHECK-NEXT: [[V1:%.*]] = load volatile <4 x float>, ptr [[V0]], align 8
; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
@@ -136,7 +300,7 @@ define <2 x float> @negative_volatile_load(ptr %arg0) {
; Kept the success case structure but mixed in a second pointer base to induce failure.
define <2 x float> @negative_different_bases(ptr %arg0, ptr %arg1) {
; CHECK-LABEL: define <2 x float> @negative_different_bases(
-; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[ARG1:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[ARG1:%.*]]) {
; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
; CHECK-NEXT: [[V0_OTHER:%.*]] = getelementptr inbounds i8, ptr [[ARG1]], i64 40
@@ -156,7 +320,7 @@ define <2 x float> @negative_different_bases(ptr %arg0, ptr %arg1) {
; Tweaked the mask to <1, 3> from the success case to create a gap.
define <2 x float> @negative_memory_gap(ptr %arg0) {
; CHECK-LABEL: define <2 x float> @negative_memory_gap(
-; CHECK-SAME: ptr [[ARG0:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 40
; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 1, i32 3>
@@ -170,7 +334,7 @@ define <2 x float> @negative_memory_gap(ptr %arg0) {
define <4 x i32> @test_element_mismatch(ptr dereferenceable(16) align 16 %p) {
; CHECK-LABEL: define <4 x i32> @test_element_mismatch(
-; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
; CHECK-NEXT: [[L0:%.*]] = load <2 x i32>, ptr [[P]], align 8
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 8
; CHECK-NEXT: [[L1:%.*]] = load <2 x float>, ptr [[P1]], align 8
@@ -188,7 +352,7 @@ define <4 x i32> @test_element_mismatch(ptr dereferenceable(16) align 16 %p) {
define <4 x i64> @test_element_mismatch_double(ptr dereferenceable(32) align 32 %p) {
; CHECK-LABEL: define <4 x i64> @test_element_mismatch_double(
-; CHECK-SAME: ptr align 32 dereferenceable(32) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 32 dereferenceable(32) [[P:%.*]]) {
; CHECK-NEXT: [[L0:%.*]] = load <2 x i64>, ptr [[P]], align 16
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 16
; CHECK-NEXT: [[L1:%.*]] = load <2 x double>, ptr [[P1]], align 16
@@ -206,7 +370,7 @@ define <4 x i64> @test_element_mismatch_double(ptr dereferenceable(32) align 32
define <4 x i32> @test_element_mismatch_i16_i32(ptr dereferenceable(16) align 16 %p) {
; CHECK-LABEL: define <4 x i32> @test_element_mismatch_i16_i32(
-; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
; CHECK-NEXT: [[L0:%.*]] = load <4 x i16>, ptr [[P]], align 8
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 8
; CHECK-NEXT: [[L1:%.*]] = load <2 x i32>, ptr [[P1]], align 8
@@ -224,7 +388,7 @@ define <4 x i32> @test_element_mismatch_i16_i32(ptr dereferenceable(16) align 16
define <4 x i64> @test_element_mismatch_i32_i64(ptr dereferenceable(32) align 32 %p) {
; CHECK-LABEL: define <4 x i64> @test_element_mismatch_i32_i64(
-; CHECK-SAME: ptr align 32 dereferenceable(32) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 32 dereferenceable(32) [[P:%.*]]) {
; CHECK-NEXT: [[L0:%.*]] = load <4 x i32>, ptr [[P]], align 16
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 16
; CHECK-NEXT: [[L1:%.*]] = load <2 x i64>, ptr [[P1]], align 16
@@ -242,7 +406,7 @@ define <4 x i64> @test_element_mismatch_i32_i64(ptr dereferenceable(32) align 32
define <4 x float> @test_shuffle_poison(ptr align 16 dereferenceable(16) %p) {
; CHECK-LABEL: define <4 x float> @test_shuffle_poison(
-; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
; CHECK-NEXT: [[L0:%.*]] = load <2 x float>, ptr [[P]], align 4
; CHECK-NEXT: [[P1:%.*]] = getelementptr i8, ptr [[P]], i64 8
; CHECK-NEXT: [[L1:%.*]] = load <2 x float>, ptr [[P1]], align 4
@@ -258,7 +422,7 @@ define <4 x float> @test_shuffle_poison(ptr align 16 dereferenceable(16) %p) {
define <2 x float> @negative_store_between_load_and_shuffle(ptr %p, float %x) {
; CHECK-LABEL: define <2 x float> @negative_store_between_load_and_shuffle(
-; CHECK-SAME: ptr [[P:%.*]], float [[X:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[P:%.*]], float [[X:%.*]]) {
; CHECK-NEXT: [[V:%.*]] = load <4 x float>, ptr [[P]], align 4
; CHECK-NEXT: [[Q:%.*]] = getelementptr float, ptr [[P]], i64 2
; CHECK-NEXT: store float [[X]], ptr [[Q]], align 4
@@ -275,7 +439,7 @@ define <2 x float> @negative_store_between_load_and_shuffle(ptr %p, float %x) {
declare void @clobber()
define <2 x float> @negative_call_between_load_and_shuffle(ptr %p) {
; CHECK-LABEL: define <2 x float> @negative_call_between_load_and_shuffle(
-; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR0]] {
+; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: [[V:%.*]] = load <4 x float>, ptr [[P]], align 4
; CHECK-NEXT: call void @clobber()
; CHECK-NEXT: [[R:%.*]] = shufflevector <4 x float> [[V]], <4 x float> poison, <2 x i32> <i32 2, i32 3>
>From 33b89ace02e08453543edaf9befc745bc4b130b8 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Tue, 2 Jun 2026 15:07:03 +0900
Subject: [PATCH 12/14] add negative testcases has gep type i32/i64
---
.../X86/fold-contiguous-loads.ll | 78 +++++++++++++++++++
1 file changed, 78 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 18c524bd01173..ee05eb02981dc 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -280,6 +280,20 @@ define <2 x float> @negative_non_contiguous(ptr %arg0) {
ret <2 x float> %v2
}
+define <2 x float> @negative_non_contiguous_gep_i32(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @negative_non_contiguous_gep_i32(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i32, ptr [[ARG0]], i64 10
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 3, i32 2>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i32, ptr %arg0, i64 10
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 3, i32 2>
+ ret <2 x float> %v2
+}
+
; 5. Negative test: Volatile load (!isSimple)
; Added the volatile attribute to the load from the success case.
define <2 x float> @negative_volatile_load(ptr %arg0) {
@@ -316,6 +330,42 @@ define <2 x float> @negative_different_bases(ptr %arg0, ptr %arg1) {
ret <2 x float> %v2
}
+define <2 x float> @negative_different_bases_gep_i32(ptr %arg0, ptr %arg1) {
+; CHECK-LABEL: define <2 x float> @negative_different_bases_gep_i32(
+; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[ARG1:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i32, ptr [[ARG0]], i64 10
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V0_OTHER:%.*]] = getelementptr inbounds i32, ptr [[ARG1]], i64 10
+; CHECK-NEXT: [[V1_OTHER:%.*]] = load <4 x float>, ptr [[V0_OTHER]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> [[V1_OTHER]], <2 x i32> <i32 2, i32 7>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i32, ptr %arg0, i64 10
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v0_other = getelementptr inbounds i32, ptr %arg1, i64 10
+ %v1_other = load <4 x float>, ptr %v0_other, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> %v1_other, <2 x i32> <i32 2, i32 7>
+ ret <2 x float> %v2
+}
+
+define <2 x float> @negative_different_bases_gep_i64(ptr %arg0, ptr %arg1) {
+; CHECK-LABEL: define <2 x float> @negative_different_bases_gep_i64(
+; CHECK-SAME: ptr [[ARG0:%.*]], ptr [[ARG1:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i64, ptr [[ARG0]], i64 5
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V0_OTHER:%.*]] = getelementptr inbounds i64, ptr [[ARG1]], i64 5
+; CHECK-NEXT: [[V1_OTHER:%.*]] = load <4 x float>, ptr [[V0_OTHER]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> [[V1_OTHER]], <2 x i32> <i32 2, i32 7>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i64, ptr %arg0, i64 5
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v0_other = getelementptr inbounds i64, ptr %arg1, i64 5
+ %v1_other = load <4 x float>, ptr %v0_other, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> %v1_other, <2 x i32> <i32 2, i32 7>
+ ret <2 x float> %v2
+}
+
; 7. Negative test: Offset continuity failure (Memory gap)
; Tweaked the mask to <1, 3> from the success case to create a gap.
define <2 x float> @negative_memory_gap(ptr %arg0) {
@@ -332,6 +382,34 @@ define <2 x float> @negative_memory_gap(ptr %arg0) {
ret <2 x float> %v2
}
+define <2 x float> @negative_memory_gap_gep_i32(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @negative_memory_gap_gep_i32(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i32, ptr [[ARG0]], i64 10
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i32, ptr %arg0, i64 10
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 1, i32 3>
+ ret <2 x float> %v2
+}
+
+define <2 x float> @negative_memory_gap_gep_i64(ptr %arg0) {
+; CHECK-LABEL: define <2 x float> @negative_memory_gap_gep_i64(
+; CHECK-SAME: ptr [[ARG0:%.*]]) {
+; CHECK-NEXT: [[V0:%.*]] = getelementptr inbounds i64, ptr [[ARG0]], i64 5
+; CHECK-NEXT: [[V1:%.*]] = load <4 x float>, ptr [[V0]], align 8
+; CHECK-NEXT: [[V2:%.*]] = shufflevector <4 x float> [[V1]], <4 x float> poison, <2 x i32> <i32 1, i32 3>
+; CHECK-NEXT: ret <2 x float> [[V2]]
+;
+ %v0 = getelementptr inbounds i64, ptr %arg0, i64 5
+ %v1 = load <4 x float>, ptr %v0, align 8
+ %v2 = shufflevector <4 x float> %v1, <4 x float> poison, <2 x i32> <i32 1, i32 3>
+ ret <2 x float> %v2
+}
+
define <4 x i32> @test_element_mismatch(ptr dereferenceable(16) align 16 %p) {
; CHECK-LABEL: define <4 x i32> @test_element_mismatch(
; CHECK-SAME: ptr align 16 dereferenceable(16) [[P:%.*]]) {
>From ae94d1e5bdccc54624dfef5143f73505e58f48a3 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Tue, 2 Jun 2026 15:50:12 +0900
Subject: [PATCH 13/14] take GEP related costs into new/old
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 2f674f69bdea3..73af92224aa3c 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -31,6 +31,7 @@
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Operator.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/KnownBits.h"
@@ -6136,15 +6137,28 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
}
InstructionCost OldCost = TTI.getInstructionCost(&I, CostKind);
- for (LoadInst *LI : Loads)
+ for (LoadInst *LI : Loads) {
OldCost += TTI.getInstructionCost(LI, CostKind);
+ if (auto *GEP = dyn_cast<GEPOperator>(LI->getPointerOperand())) {
+ SmallVector<const Value *> Indices(GEP->indices());
+ OldCost +=
+ TTI.getGEPCost(GEP->getSourceElementType(), GEP->getPointerOperand(),
+ Indices, LI->getType(), CostKind);
+ }
+ }
int64_t StartByteOffset = ExpectedBaseBitOffset / 8;
+ Type *IndexTy = DL->getIndexType(CommonBase->getType());
+ auto *StartByteOffsetValue = ConstantInt::get(IndexTy, StartByteOffset,
+ /*isSigned=*/true);
int64_t OffsetFromFirstLoad = StartByteOffset - FirstLoadOffset;
Align NewAlign = commonAlignment(FirstLI->getAlign(), OffsetFromFirstLoad);
InstructionCost NewCost =
TTI.getMemoryOpCost(Instruction::Load, VT, NewAlign,
FirstLI->getPointerAddressSpace(), CostKind);
+ SmallVector<const Value *> NewIndices = {StartByteOffsetValue};
+ NewCost +=
+ TTI.getGEPCost(Builder.getInt8Ty(), CommonBase, NewIndices, VT, CostKind);
LLVM_DEBUG(dbgs() << "Found contiguous loads to fold: " << I
<< "\n OldCost: " << OldCost << " vs NewCost: " << NewCost
>From a18e9cc8765736e21ccfc835579fb03cd223ffea Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Tue, 2 Jun 2026 16:21:26 +0900
Subject: [PATCH 14/14] Add comments to explain the key logic with minor
refactoring
---
.../Transforms/Vectorize/VectorCombine.cpp | 101 ++++++++++++------
1 file changed, 69 insertions(+), 32 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 73af92224aa3c..409eb5b87469d 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6055,14 +6055,42 @@ bool VectorCombine::shrinkPhiOfShuffles(Instruction &I) {
return true;
}
-/// Check if a vector instruction's lanes originate from contiguous memory
-/// accesses. Fold the original loads and shuffles into a single vector load
-/// if it is profitable. For example:
-/// shufflevector(load <4 x float> ptr), poison, <2, 3>
-/// -> load <2 x float> (ptradd ptr, 8)
-/// Cost model calculations take into account the cost of the original
-/// unique load(s) and the target instruction versus the cost of the new
-/// aligned vector load.
+/// Try to fold lanes assembled from contiguous vector-load elements into one
+/// load of the result type.
+///
+/// 1. Trace lanes:
+/// result lane 0 result lane 1 ... result lane N
+/// | | |
+/// +------- look through shuffles ---------+
+/// |
+/// source load + source lane
+///
+/// 2. Check layout:
+/// same base pointer and contiguous offsets?
+///
+/// 3. Model old cost:
+/// current op + unique loads + original GEPs
+///
+/// 4. Model new cost:
+/// ptradd(base, start byte offset) + one vector load
+///
+/// 5. Replace:
+/// if NewCost is cheaper
+///
+/// For example:
+///
+/// %p = getelementptr float, ptr %base, i64 4
+/// %v = load <4 x float>, ptr %p
+/// base+16 base+20 base+24 base+28
+/// lane 0 lane 1 lane 2 lane 3
+/// | |
+/// +---------+ contiguous
+/// |
+/// %r = shufflevector %v, poison, <2, 3>
+/// |
+/// v
+/// %q = getelementptr i8, ptr %base, i64 24
+/// %r = load <2 x float>, ptr %q
bool VectorCombine::foldContiguousLoads(Instruction &I) {
auto *VT = dyn_cast<FixedVectorType>(I.getType());
if (!VT || I.use_empty())
@@ -6076,13 +6104,15 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
uint64_t ElementSizeBits = DL->getTypeStoreSizeInBits(EltTy);
assert((ElementSizeBits <= MaxInt64) && "element size far too large?");
- int64_t ElementSize = static_cast<int64_t>(ElementSizeBits);
+ int64_t ElementSizeBitsI64 = static_cast<int64_t>(ElementSizeBits);
unsigned NumElts = VT->getNumElements();
Value *CommonBase = nullptr;
- int64_t ExpectedBaseBitOffset = 0, FirstLoadOffset = 0;
+ int64_t StartBitOffset = 0, FirstLoadByteOffset = 0;
LoadInst *FirstLI = nullptr;
SmallPtrSet<LoadInst *, 4> Loads;
for (unsigned Lane = 0; Lane < NumElts; ++Lane) {
+ // Step 1: Trace this result lane through shuffle users to find the source
+ // instruction and the lane selected from it.
InstLane IL = lookThroughShuffles(&I, Lane);
if (!IL.first)
return false;
@@ -6105,37 +6135,43 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
MemoryLocation::get(LI), AA))
return false;
- int64_t ConstantOffset = 0;
+ // Step 2: Convert the load pointer and selected source lane into an
+ // absolute bit offset: byte offset of the load pointer plus lane offset
+ // within the load.
+ int64_t LoadByteOffset = 0;
Value *Base = GetPointerBaseWithConstantOffset(LI->getPointerOperand(),
- ConstantOffset, *DL);
- int64_t AbsoluteBitOffset =
- (ConstantOffset * 8) + (IL.second * ElementSize);
+ LoadByteOffset, *DL);
+ int64_t SourceLaneBitOffset =
+ (LoadByteOffset * 8) + (IL.second * ElementSizeBitsI64);
if (Lane == 0) {
- if (AbsoluteBitOffset % 8 != 0)
+ if (SourceLaneBitOffset % 8 != 0)
return false;
CommonBase = Base;
- ExpectedBaseBitOffset = AbsoluteBitOffset;
+ StartBitOffset = SourceLaneBitOffset;
FirstLI = LI;
- FirstLoadOffset = ConstantOffset;
+ FirstLoadByteOffset = LoadByteOffset;
} else {
+ // Step 2: All later result lanes must use the same underlying base
+ // pointer and appear at the element-stride offset expected from the first
+ // result lane.
if (Base != CommonBase)
return false;
- uint64_t ResultLane = Lane;
- assert(ResultLane <= MaxInt64 / static_cast<uint64_t>(ElementSize) &&
- "result lane offset far too large?");
+ assert(Lane <= MaxInt64 / static_cast<uint64_t>(ElementSizeBitsI64) &&
+ "lane offset far too large?");
int64_t ExpectedBitOffset =
- ExpectedBaseBitOffset +
- static_cast<int64_t>(ResultLane) * ElementSize;
- if (AbsoluteBitOffset != ExpectedBitOffset)
+ StartBitOffset + static_cast<int64_t>(Lane) * ElementSizeBitsI64;
+ if (SourceLaneBitOffset != ExpectedBitOffset)
return false;
}
Loads.insert(LI);
}
+ // Step 3: Model the current form: the shuffle-like instruction, each unique
+ // source load, and any GEP used to compute those load addresses.
InstructionCost OldCost = TTI.getInstructionCost(&I, CostKind);
for (LoadInst *LI : Loads) {
OldCost += TTI.getInstructionCost(LI, CostKind);
@@ -6147,12 +6183,15 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
}
}
- int64_t StartByteOffset = ExpectedBaseBitOffset / 8;
+ int64_t StartByteOffset = StartBitOffset / 8;
Type *IndexTy = DL->getIndexType(CommonBase->getType());
auto *StartByteOffsetValue = ConstantInt::get(IndexTy, StartByteOffset,
/*isSigned=*/true);
- int64_t OffsetFromFirstLoad = StartByteOffset - FirstLoadOffset;
- Align NewAlign = commonAlignment(FirstLI->getAlign(), OffsetFromFirstLoad);
+ int64_t ByteOffsetFromFirstLoad = StartByteOffset - FirstLoadByteOffset;
+ Align NewAlign =
+ commonAlignment(FirstLI->getAlign(), ByteOffsetFromFirstLoad);
+ // Step 4: Model the replacement: one vector load from the adjusted alignment
+ // and the byte-offset GEP that CreatePtrAdd will emit.
InstructionCost NewCost =
TTI.getMemoryOpCost(Instruction::Load, VT, NewAlign,
FirstLI->getPointerAddressSpace(), CostKind);
@@ -6167,13 +6206,11 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (OldCost <= NewCost)
return false;
- Type *IndexTy = DL->getIndexType(CommonBase->getType());
- Value *NewBasePtr = Builder.CreatePtrAdd(
- CommonBase, ConstantInt::get(IndexTy, StartByteOffset,
- /*isSigned=*/true));
+ // Step 5: Emit the same byte-offset GEP modeled above, then load the
+ // contiguous result vector from it.
+ Value *NewBasePtr = Builder.CreatePtrAdd(CommonBase, StartByteOffsetValue);
LoadInst *NewLoad = Builder.CreateAlignedLoad(VT, NewBasePtr, NewAlign);
- if (Loads.size() == 1)
- copyMetadataForLoad(*NewLoad, *FirstLI);
+ copyMetadataForLoad(*NewLoad, *FirstLI);
replaceValue(I, *NewLoad);
return true;
More information about the llvm-commits
mailing list