[llvm] [VectorCombine] Fold contiguous loads into a single vector load (PR #185736)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 23 00:21:51 PDT 2026
https://github.com/ParkHanbum updated https://github.com/llvm/llvm-project/pull/185736
>From c05402efc497410d2effa51fa56197ec20ac6aa1 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/20] 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 cf51d862b179fdf14a3715c6ef6f4c032332ec7f 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/20] [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 741bd4460a5ab..e38281c753d5f 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -162,6 +162,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');
@@ -6426,6 +6427,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() {
@@ -6535,6 +6621,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 46baa1c71d28c21e7509545f0905da3d504fc69b 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 03/20] Add testcasess to verify bailout on element type
mismatch
---
.../Transforms/Vectorize/VectorCombine.cpp | 4 +-
.../X86/fold-contiguous-loads.ll | 73 +++++++++++++++++++
2 files changed, 75 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index e38281c753d5f..e9cfc0fce97e0 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6448,11 +6448,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;
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 595acf86daae4481a3a0e31d18fb3f06dc6bc306 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 04/20] 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 938518ec498fd1bed63cc22648b95084c2524120 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 05/20] 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 bdccc0519f8e6070a7d8413b044c672c964a930c Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 16:34:37 +0900
Subject: [PATCH 06/20] 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 882f1934c9b9751beeb95645fa34bd4da33c6951 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 16:39:46 +0900
Subject: [PATCH 07/20] add checks for memory correctness
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index e9cfc0fce97e0..514d719a5be9f 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6463,6 +6463,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 860ef12212842d500055130ee631e67d7e184927 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 16:40:12 +0900
Subject: [PATCH 08/20] 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 ff81f86d7fdd12589c4f2a3e956f511599d4e528 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 30 May 2026 17:08:19 +0900
Subject: [PATCH 09/20] minor fixes
---
.../Transforms/Vectorize/VectorCombine.cpp | 37 +++--
.../X86/fold-contiguous-loads.ll | 152 +++++++++++++++---
2 files changed, 157 insertions(+), 32 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 514d719a5be9f..8e25eeaad2f91 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6440,9 +6440,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;
@@ -6487,7 +6494,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;
}
@@ -6502,20 +6516,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);
- NewLoad->copyMetadata(*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..0e33f8484b7b4 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -1,10 +1,10 @@
; 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) {
; 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]]
@@ -18,7 +18,7 @@ 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) {
; 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]]
@@ -31,7 +31,7 @@ define <2 x double> @extract_subvector_middle(ptr %arg0) {
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]]
;
@@ -44,7 +44,7 @@ define <3 x i32> @test_odd_number_elements(ptr align 16 dereferenceable(16) %p)
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]]
;
@@ -57,7 +57,7 @@ define <4 x i32> @test_odd_number_load_to_even_svi(ptr align 16 dereferenceable(
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]]
;
@@ -70,7 +70,7 @@ define <11 x i32> @test_odd_to_odd_large_mask(ptr align 64 dereferenceable(64) %
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]]
@@ -86,7 +86,7 @@ define <5 x i16> @test_load_17_mask_5_mid_offset_i16(ptr align 16 dereferenceabl
; 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 +104,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 +120,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 +136,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 +156,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 +170,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 +188,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 +206,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 +224,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 +242,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 +258,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
@@ -268,15 +268,14 @@ 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
}
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>
@@ -284,7 +283,114 @@ 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
}
+
+define <2 x float> @preserve_load_metadata_for_single_attributed_load(ptr %p) {
+; CHECK-LABEL: define <2 x float> @preserve_load_metadata_for_single_attributed_load(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 48
+; CHECK-NEXT: [[R:%.*]] = load <2 x float>, ptr [[TMP1]], align 8, !tbaa [[FLOAT_TBAA0:![0-9]+]], !alias.scope [[META3:![0-9]+]], !noalias [[META3]]
+; CHECK-NEXT: ret <2 x float> [[R]]
+;
+ %q = getelementptr inbounds i8, ptr %p, i64 40
+ %v = load <4 x float>, ptr %q, align 8, !tbaa !0, !alias.scope !3, !noalias !3
+ %r = shufflevector <4 x float> %v, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %r
+}
+
+define <3 x i32> @drop_load_metadata_for_multiple_attributed_loads(ptr %p) {
+; CHECK-LABEL: define <3 x i32> @drop_load_metadata_for_multiple_attributed_loads(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[R:%.*]] = load <3 x i32>, ptr [[P]], align 8
+; CHECK-NEXT: ret <3 x i32> [[R]]
+;
+ %l0 = load <2 x i32>, ptr %p, align 8, !alias.scope !3, !noalias !3
+ %p1 = getelementptr i8, ptr %p, i64 8
+ %l1 = load <2 x i32>, ptr %p1, align 8, !alias.scope !3, !noalias !3
+ %r = shufflevector <2 x i32> %l0, <2 x i32> %l1, <3 x i32> <i32 0, i32 1, i32 2>
+ ret <3 x i32> %r
+}
+
+define <3 x i32> @preserve_nusw_gep(ptr %p) {
+; CHECK-LABEL: define <3 x i32> @preserve_nusw_gep(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 40
+; CHECK-NEXT: [[R:%.*]] = load <3 x i32>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <3 x i32> [[R]]
+;
+ %q = getelementptr nusw i8, ptr %p, i64 40
+ %l0 = load <2 x i32>, ptr %q, align 8
+ %q1 = getelementptr i8, ptr %q, i64 8
+ %l1 = load <2 x i32>, ptr %q1, align 8
+ %r = shufflevector <2 x i32> %l0, <2 x i32> %l1, <3 x i32> <i32 0, i32 1, i32 2>
+ ret <3 x i32> %r
+}
+
+define <3 x i32> @preserve_nusw_gep_negative_offset(ptr %p) {
+; CHECK-LABEL: define <3 x i32> @preserve_nusw_gep_negative_offset(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 -40
+; CHECK-NEXT: [[R:%.*]] = load <3 x i32>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <3 x i32> [[R]]
+;
+ %q = getelementptr nusw i8, ptr %p, i64 -40
+ %l0 = load <2 x i32>, ptr %q, align 8
+ %q1 = getelementptr i8, ptr %q, i64 8
+ %l1 = load <2 x i32>, ptr %q1, align 8
+ %r = shufflevector <2 x i32> %l0, <2 x i32> %l1, <3 x i32> <i32 0, i32 1, i32 2>
+ ret <3 x i32> %r
+}
+
+define <2 x i32> @drop_nusw_gep_sign_change(ptr %p) {
+; CHECK-LABEL: define <2 x i32> @drop_nusw_gep_sign_change(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 4
+; CHECK-NEXT: [[R:%.*]] = load <2 x i32>, ptr [[TMP1]], align 4
+; CHECK-NEXT: ret <2 x i32> [[R]]
+;
+ %q = getelementptr nusw i8, ptr %p, i64 -4
+ %l0 = load <4 x i32>, ptr %q, align 4
+ %r = shufflevector <4 x i32> %l0, <4 x i32> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x i32> %r
+}
+
+define <2 x float> @negative_load_in_different_block(ptr %p, i1 %c) {
+; CHECK-LABEL: define <2 x float> @negative_load_in_different_block(
+; CHECK-SAME: ptr [[P:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[V:%.*]] = load <4 x float>, ptr [[P]], align 4
+; CHECK-NEXT: br i1 [[C]], label %[[USE:.*]], label %[[OTHER:.*]]
+; CHECK: [[USE]]:
+; 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]]
+; CHECK: [[OTHER]]:
+; CHECK-NEXT: ret <2 x float> zeroinitializer
+;
+entry:
+ %v = load <4 x float>, ptr %p, align 4
+ br i1 %c, label %use, label %other
+
+use:
+ %r = shufflevector <4 x float> %v, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %r
+
+other:
+ ret <2 x float> zeroinitializer
+}
+
+!0 = !{!1, !1, i64 0}
+!1 = !{!"float", !2, i64 0}
+!2 = !{!"Simple C/C++ TBAA"}
+!3 = !{!4}
+!4 = distinct !{!4, !5}
+!5 = distinct !{!5}
+;.
+; CHECK: [[FLOAT_TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0}
+; CHECK: [[META1]] = !{!"float", [[META2:![0-9]+]], i64 0}
+; CHECK: [[META2]] = !{!"Simple C/C++ TBAA"}
+; CHECK: [[META3]] = !{[[META4:![0-9]+]]}
+; CHECK: [[META4]] = distinct !{[[META4]], [[META5:![0-9]+]]}
+; CHECK: [[META5]] = distinct !{[[META5]]}
+;.
>From 0df4e1398c129b89c2dc71a30be5d97065873b09 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 10/20] add more testcases with GEP type i32/i64
updated AliveProof: https://alive2.llvm.org/ce/z/JyMsSC
---
.../X86/fold-contiguous-loads.ll | 164 ++++++++++++++++++
1 file changed, 164 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 0e33f8484b7b4..2216c36a5b7cd 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -15,6 +15,34 @@ 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 inbounds 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 inbounds 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(
@@ -29,6 +57,32 @@ 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 inbounds 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 inbounds 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:%.*]]) {
@@ -42,6 +96,32 @@ 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:%.*]]) {
@@ -55,6 +135,33 @@ 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:%.*]]) {
@@ -68,6 +175,33 @@ 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:%.*]]) {
@@ -82,6 +216,36 @@ 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) {
>From bbfd2210ed82b2d1ddbbe5100e8988343b2ef3e0 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 11/20] add negative testcases has gep type i32/i64
---
.../X86/fold-contiguous-loads.ll | 86 ++++++++++++++++++-
1 file changed, 82 insertions(+), 4 deletions(-)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 2216c36a5b7cd..4d41551a64a55 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -19,7 +19,7 @@ define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
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 inbounds i8, ptr [[ARG0]], i64 48
+; 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]]
;
@@ -33,7 +33,7 @@ define <2 x float> @extract_subvector_with_offset_gep_i32(ptr %arg0) {
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 inbounds i8, ptr [[ARG0]], i64 48
+; 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]]
;
@@ -60,7 +60,7 @@ define <2 x double> @extract_subvector_middle(ptr %arg0) {
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 inbounds i8, ptr [[ARG0]], i64 48
+; 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]]
;
@@ -73,7 +73,7 @@ define <2 x double> @extract_subvector_middle_gep_i32(ptr %arg0) {
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 inbounds i8, ptr [[ARG0]], i64 48
+; 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]]
;
@@ -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 c400c86ece2d20217985a934d04037b5ae30c400 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 12/20] take GEP related costs into new/old
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 8e25eeaad2f91..62633ac0c03ab 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"
@@ -6509,15 +6510,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
@@ -6526,7 +6540,6 @@ 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));
>From f34c5e9caf47d5d8c00f57a03734930dd57a824f 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 13/20] Add comments to explain the key logic with minor
refactoring
---
.../Transforms/Vectorize/VectorCombine.cpp | 97 +++++++++++++------
1 file changed, 68 insertions(+), 29 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 62633ac0c03ab..1b7b524f3a8f3 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6428,14 +6428,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())
@@ -6449,13 +6477,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;
@@ -6478,37 +6508,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);
@@ -6520,12 +6556,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);
@@ -6540,9 +6579,9 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (OldCost <= NewCost)
return false;
- 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);
>From d4cef17913316169bcf92b4f955cd3fb1019cf1e Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Fri, 5 Jun 2026 00:10:29 +0900
Subject: [PATCH 14/20] preserves attributes for GEP
---
.../Transforms/Vectorize/VectorCombine.cpp | 30 ++++++++++++++++++-
.../X86/fold-contiguous-loads.ll | 18 +++++------
2 files changed, 38 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 1b7b524f3a8f3..d0079e1d0972b 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6428,6 +6428,26 @@ bool VectorCombine::shrinkPhiOfShuffles(Instruction &I) {
return true;
}
+static GEPNoWrapFlags getConstantGEPNoWrapFlagsToBase(Value *Ptr, Value *Base,
+ const DataLayout &DL) {
+ std::optional<GEPNoWrapFlags> Flags;
+ while (Ptr != Base) {
+ auto *GEP = dyn_cast<GEPOperator>(Ptr);
+ if (!GEP)
+ return GEPNoWrapFlags::none();
+
+ APInt Offset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
+ if (!GEP->accumulateConstantOffset(DL, Offset))
+ return GEPNoWrapFlags::none();
+
+ Flags = Flags ? Flags->intersectForOffsetAdd(GEP->getNoWrapFlags())
+ : GEP->getNoWrapFlags();
+ Ptr = GEP->getPointerOperand();
+ }
+
+ return Flags.value_or(GEPNoWrapFlags::none());
+}
+
/// Try to fold lanes assembled from contiguous vector-load elements into one
/// load of the result type.
///
@@ -6482,6 +6502,7 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
Value *CommonBase = nullptr;
int64_t StartBitOffset = 0, FirstLoadByteOffset = 0;
LoadInst *FirstLI = nullptr;
+ GEPNoWrapFlags NewGEPFlags = GEPNoWrapFlags::none();
SmallPtrSet<LoadInst *, 4> Loads;
for (unsigned Lane = 0; Lane < NumElts; ++Lane) {
// Step 1: Trace this result lane through shuffle users to find the source
@@ -6524,6 +6545,12 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
StartBitOffset = SourceLaneBitOffset;
FirstLI = LI;
FirstLoadByteOffset = LoadByteOffset;
+ NewGEPFlags = getConstantGEPNoWrapFlagsToBase(LI->getPointerOperand(),
+ CommonBase, *DL);
+ // The selected lane is inside the original vector load's memory range.
+ if (IL.second != 0)
+ NewGEPFlags =
+ NewGEPFlags.intersectForOffsetAdd(GEPNoWrapFlags::inBounds());
} 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
@@ -6581,7 +6608,8 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
// Step 5: Emit the same byte-offset GEP modeled above, then load the
// contiguous result vector from it.
- Value *NewBasePtr = Builder.CreatePtrAdd(CommonBase, StartByteOffsetValue);
+ Value *NewBasePtr =
+ Builder.CreatePtrAdd(CommonBase, StartByteOffsetValue, "", NewGEPFlags);
LoadInst *NewLoad = Builder.CreateAlignedLoad(VT, NewBasePtr, NewAlign);
if (Loads.size() == 1)
copyMetadataForLoad(*NewLoad, *FirstLI);
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 4d41551a64a55..88f2fe5d4cde4 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -5,7 +5,7 @@
define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
; CHECK-LABEL: define <2 x float> @extract_subvector_with_offset(
; CHECK-SAME: ptr [[ARG0:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[TMP2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x float> [[TMP2]]
;
@@ -19,7 +19,7 @@ define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
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: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[V2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x float> [[V2]]
;
@@ -33,7 +33,7 @@ define <2 x float> @extract_subvector_with_offset_gep_i32(ptr %arg0) {
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: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[V2:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x float> [[V2]]
;
@@ -47,7 +47,7 @@ define <2 x float> @extract_subvector_with_offset_gep_i64(ptr %arg0) {
define <2 x double> @extract_subvector_middle(ptr %arg0) {
; CHECK-LABEL: define <2 x double> @extract_subvector_middle(
; CHECK-SAME: ptr [[ARG0:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[ARG0]], i64 48
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x double> [[V2]]
;
@@ -60,7 +60,7 @@ define <2 x double> @extract_subvector_middle(ptr %arg0) {
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: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x double> [[V2]]
;
@@ -73,7 +73,7 @@ define <2 x double> @extract_subvector_middle_gep_i32(ptr %arg0) {
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: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[ARG0]], i64 48
; CHECK-NEXT: [[V2:%.*]] = load <2 x double>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <2 x double> [[V2]]
;
@@ -532,7 +532,7 @@ define <2 x float> @negative_call_between_load_and_shuffle(ptr %p) {
define <2 x float> @preserve_load_metadata_for_single_attributed_load(ptr %p) {
; CHECK-LABEL: define <2 x float> @preserve_load_metadata_for_single_attributed_load(
; CHECK-SAME: ptr [[P:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 48
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 48
; CHECK-NEXT: [[R:%.*]] = load <2 x float>, ptr [[TMP1]], align 8, !tbaa [[FLOAT_TBAA0:![0-9]+]], !alias.scope [[META3:![0-9]+]], !noalias [[META3]]
; CHECK-NEXT: ret <2 x float> [[R]]
;
@@ -558,7 +558,7 @@ define <3 x i32> @drop_load_metadata_for_multiple_attributed_loads(ptr %p) {
define <3 x i32> @preserve_nusw_gep(ptr %p) {
; CHECK-LABEL: define <3 x i32> @preserve_nusw_gep(
; CHECK-SAME: ptr [[P:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 40
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr nusw i8, ptr [[P]], i64 40
; CHECK-NEXT: [[R:%.*]] = load <3 x i32>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <3 x i32> [[R]]
;
@@ -573,7 +573,7 @@ define <3 x i32> @preserve_nusw_gep(ptr %p) {
define <3 x i32> @preserve_nusw_gep_negative_offset(ptr %p) {
; CHECK-LABEL: define <3 x i32> @preserve_nusw_gep_negative_offset(
; CHECK-SAME: ptr [[P:%.*]]) {
-; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 -40
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr nusw i8, ptr [[P]], i64 -40
; CHECK-NEXT: [[R:%.*]] = load <3 x i32>, ptr [[TMP1]], align 8
; CHECK-NEXT: ret <3 x i32> [[R]]
;
>From f91fe555ce5ab386cf5e9f4e4e989e8e9dba769e Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Sat, 6 Jun 2026 01:26:54 +0900
Subject: [PATCH 15/20] Use APInt load offsets
---
.../Transforms/Vectorize/VectorCombine.cpp | 58 ++++++++++---------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index d0079e1d0972b..18f28e1a09043 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6493,16 +6493,22 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
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 ElementSizeBitsI64 = static_cast<int64_t>(ElementSizeBits);
+ if (ElementSizeBits % 8 != 0)
+ return false;
+ uint64_t ElementSizeBytes = ElementSizeBits / 8;
unsigned NumElts = VT->getNumElements();
Value *CommonBase = nullptr;
- int64_t StartBitOffset = 0, FirstLoadByteOffset = 0;
+ APInt StartByteOffset(1, 0), FirstLoadByteOffset(1, 0);
LoadInst *FirstLI = nullptr;
GEPNoWrapFlags NewGEPFlags = GEPNoWrapFlags::none();
+ auto GetLaneByteOffset = [ElementSizeBytes](uint64_t Lane,
+ unsigned IndexBits) {
+ return APInt(IndexBits, Lane, /*isSigned=*/false,
+ /*implicitTrunc=*/true) *
+ APInt(IndexBits, ElementSizeBytes, /*isSigned=*/false,
+ /*implicitTrunc=*/true);
+ };
SmallPtrSet<LoadInst *, 4> Loads;
for (unsigned Lane = 0; Lane < NumElts; ++Lane) {
// Step 1: Trace this result lane through shuffle users to find the source
@@ -6529,22 +6535,21 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
MemoryLocation::get(LI), AA))
return false;
- // 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.
+ // Step 2: Convert the load pointer and selected source lane into a byte
+ // offset in the pointer index type.
int64_t LoadByteOffset = 0;
Value *Base = GetPointerBaseWithConstantOffset(LI->getPointerOperand(),
LoadByteOffset, *DL);
- int64_t SourceLaneBitOffset =
- (LoadByteOffset * 8) + (IL.second * ElementSizeBitsI64);
+ unsigned IndexBits = DL->getIndexTypeSizeInBits(Base->getType());
+ APInt LoadByteOffsetAP(IndexBits, LoadByteOffset, /*isSigned=*/true);
+ APInt SourceByteOffset =
+ LoadByteOffsetAP +
+ GetLaneByteOffset(static_cast<uint64_t>(IL.second), IndexBits);
if (Lane == 0) {
- if (SourceLaneBitOffset % 8 != 0)
- return false;
-
CommonBase = Base;
- StartBitOffset = SourceLaneBitOffset;
+ StartByteOffset = SourceByteOffset;
FirstLI = LI;
- FirstLoadByteOffset = LoadByteOffset;
+ FirstLoadByteOffset = LoadByteOffsetAP;
NewGEPFlags = getConstantGEPNoWrapFlagsToBase(LI->getPointerOperand(),
CommonBase, *DL);
// The selected lane is inside the original vector load's memory range.
@@ -6557,13 +6562,12 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
// result lane.
if (Base != CommonBase)
return false;
+ if (IndexBits != StartByteOffset.getBitWidth())
+ return false;
- assert(Lane <= MaxInt64 / static_cast<uint64_t>(ElementSizeBitsI64) &&
- "lane offset far too large?");
-
- int64_t ExpectedBitOffset =
- StartBitOffset + static_cast<int64_t>(Lane) * ElementSizeBitsI64;
- if (SourceLaneBitOffset != ExpectedBitOffset)
+ APInt ExpectedByteOffset =
+ StartByteOffset + GetLaneByteOffset(Lane, IndexBits);
+ if (SourceByteOffset != ExpectedByteOffset)
return false;
}
@@ -6583,13 +6587,15 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
}
}
- int64_t StartByteOffset = StartBitOffset / 8;
Type *IndexTy = DL->getIndexType(CommonBase->getType());
- auto *StartByteOffsetValue = ConstantInt::get(IndexTy, StartByteOffset,
- /*isSigned=*/true);
- int64_t ByteOffsetFromFirstLoad = StartByteOffset - FirstLoadByteOffset;
+ auto *StartByteOffsetValue = ConstantInt::get(IndexTy, StartByteOffset);
+ APInt ByteOffsetFromFirstLoad = StartByteOffset - FirstLoadByteOffset;
+ unsigned AlignOffsetBits =
+ std::min<unsigned>(ByteOffsetFromFirstLoad.getBitWidth(), 64);
+ uint64_t ByteOffsetFromFirstLoadForAlign =
+ ByteOffsetFromFirstLoad.getLoBits(AlignOffsetBits).getZExtValue();
Align NewAlign =
- commonAlignment(FirstLI->getAlign(), ByteOffsetFromFirstLoad);
+ commonAlignment(FirstLI->getAlign(), ByteOffsetFromFirstLoadForAlign);
// Step 4: Model the replacement: one vector load from the adjusted alignment
// and the byte-offset GEP that CreatePtrAdd will emit.
InstructionCost NewCost =
>From f01762eafa1b3235287c1af8ce4ddc6db45fe381 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Sat, 6 Jun 2026 01:27:18 +0900
Subject: [PATCH 16/20] Limit folded GEP cost
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 18f28e1a09043..a42ed35791ecc 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6579,7 +6579,8 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
InstructionCost OldCost = TTI.getInstructionCost(&I, CostKind);
for (LoadInst *LI : Loads) {
OldCost += TTI.getInstructionCost(LI, CostKind);
- if (auto *GEP = dyn_cast<GEPOperator>(LI->getPointerOperand())) {
+ if (auto *GEP = dyn_cast<GetElementPtrInst>(LI->getPointerOperand());
+ GEP && GEP->hasOneUse()) {
SmallVector<const Value *> Indices(GEP->indices());
OldCost +=
TTI.getGEPCost(GEP->getSourceElementType(), GEP->getPointerOperand(),
>From 26d2a1249bdbb700daad47e2462e033c4d6aff6b Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Sat, 6 Jun 2026 01:27:50 +0900
Subject: [PATCH 17/20] Add large offset test
---
.../VectorCombine/X86/fold-contiguous-loads.ll | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index 88f2fe5d4cde4..b21e71721a97e 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -15,6 +15,19 @@ define <2 x float> @extract_subvector_with_offset(ptr %arg0) {
ret <2 x float> %v2
}
+define <2 x float> @large_offset_without_signed_overflow(ptr %p) {
+; CHECK-LABEL: define <2 x float> @large_offset_without_signed_overflow(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 -9223372036854775808
+; CHECK-NEXT: [[R:%.*]] = load <2 x float>, ptr [[TMP1]], align 8
+; CHECK-NEXT: ret <2 x float> [[R]]
+;
+ %q = getelementptr i8, ptr %p, i64 9223372036854775800
+ %v = load <4 x float>, ptr %q, align 8
+ %r = shufflevector <4 x float> %v, <4 x float> poison, <2 x i32> <i32 2, i32 3>
+ ret <2 x float> %r
+}
+
; 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(
>From a77c4691c8f5d22bf5adaa31565116b867987038 Mon Sep 17 00:00:00 2001
From: Hanbum Park <kese111 at gmail.com>
Date: Sat, 6 Jun 2026 01:28:21 +0900
Subject: [PATCH 18/20] Strengthen metadata drop test
---
.../X86/fold-contiguous-loads.ll | 23 +++++++++++--------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
index b21e71721a97e..ff8dcf4d479f8 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -555,17 +555,20 @@ define <2 x float> @preserve_load_metadata_for_single_attributed_load(ptr %p) {
ret <2 x float> %r
}
-define <3 x i32> @drop_load_metadata_for_multiple_attributed_loads(ptr %p) {
-; CHECK-LABEL: define <3 x i32> @drop_load_metadata_for_multiple_attributed_loads(
+define <2 x i1> @drop_load_metadata_for_multiple_attributed_loads(ptr %p) {
+; CHECK-LABEL: define <2 x i1> @drop_load_metadata_for_multiple_attributed_loads(
; CHECK-SAME: ptr [[P:%.*]]) {
-; CHECK-NEXT: [[R:%.*]] = load <3 x i32>, ptr [[P]], align 8
-; CHECK-NEXT: ret <3 x i32> [[R]]
+; CHECK-NEXT: [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 1
+; CHECK-NEXT: [[R:%.*]] = load <2 x i8>, ptr [[TMP1]], align 1{{$}}
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult <2 x i8> [[R]], splat (i8 2)
+; CHECK-NEXT: ret <2 x i1> [[CMP]]
;
- %l0 = load <2 x i32>, ptr %p, align 8, !alias.scope !3, !noalias !3
- %p1 = getelementptr i8, ptr %p, i64 8
- %l1 = load <2 x i32>, ptr %p1, align 8, !alias.scope !3, !noalias !3
- %r = shufflevector <2 x i32> %l0, <2 x i32> %l1, <3 x i32> <i32 0, i32 1, i32 2>
- ret <3 x i32> %r
+ %l0 = load <2 x i8>, ptr %p, align 2, !range !6
+ %p1 = getelementptr i8, ptr %p, i64 2
+ %l1 = load <2 x i8>, ptr %p1, align 2, !range !7
+ %r = shufflevector <2 x i8> %l0, <2 x i8> %l1, <2 x i32> <i32 1, i32 2>
+ %cmp = icmp ult <2 x i8> %r, <i8 2, i8 2>
+ ret <2 x i1> %cmp
}
define <3 x i32> @preserve_nusw_gep(ptr %p) {
@@ -641,6 +644,8 @@ other:
!3 = !{!4}
!4 = distinct !{!4, !5}
!5 = distinct !{!5}
+!6 = !{i8 0, i8 2}
+!7 = !{i8 2, i8 4}
;.
; CHECK: [[FLOAT_TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0}
; CHECK: [[META1]] = !{!"float", [[META2:![0-9]+]], i64 0}
>From 0087431de14df4fcd67260746a10daa4120ed453 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Mon, 29 Jun 2026 16:12:13 +0900
Subject: [PATCH 19/20] Use getTypeStoreSize for element byte size
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index a42ed35791ecc..6fb199bbeb64c 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6493,10 +6493,7 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (!DL->typeSizeEqualsStoreSize(EltTy))
return false;
- uint64_t ElementSizeBits = DL->getTypeStoreSizeInBits(EltTy);
- if (ElementSizeBits % 8 != 0)
- return false;
- uint64_t ElementSizeBytes = ElementSizeBits / 8;
+ uint64_t ElementSizeBytes = DL->getTypeStoreSize(EltTy);
unsigned NumElts = VT->getNumElements();
Value *CommonBase = nullptr;
APInt StartByteOffset(1, 0), FirstLoadByteOffset(1, 0);
>From 6487c2d8b52b2b7f38a7764fcad159b0a9ca09fb Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Thu, 23 Jul 2026 16:07:45 +0900
Subject: [PATCH 20/20] Simplify lane byte offset calculation
---
llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 6fb199bbeb64c..64e53f920f0ee 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6499,13 +6499,6 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
APInt StartByteOffset(1, 0), FirstLoadByteOffset(1, 0);
LoadInst *FirstLI = nullptr;
GEPNoWrapFlags NewGEPFlags = GEPNoWrapFlags::none();
- auto GetLaneByteOffset = [ElementSizeBytes](uint64_t Lane,
- unsigned IndexBits) {
- return APInt(IndexBits, Lane, /*isSigned=*/false,
- /*implicitTrunc=*/true) *
- APInt(IndexBits, ElementSizeBytes, /*isSigned=*/false,
- /*implicitTrunc=*/true);
- };
SmallPtrSet<LoadInst *, 4> Loads;
for (unsigned Lane = 0; Lane < NumElts; ++Lane) {
// Step 1: Trace this result lane through shuffle users to find the source
@@ -6541,7 +6534,7 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
APInt LoadByteOffsetAP(IndexBits, LoadByteOffset, /*isSigned=*/true);
APInt SourceByteOffset =
LoadByteOffsetAP +
- GetLaneByteOffset(static_cast<uint64_t>(IL.second), IndexBits);
+ static_cast<uint64_t>(IL.second) * ElementSizeBytes;
if (Lane == 0) {
CommonBase = Base;
StartByteOffset = SourceByteOffset;
@@ -6562,8 +6555,7 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
if (IndexBits != StartByteOffset.getBitWidth())
return false;
- APInt ExpectedByteOffset =
- StartByteOffset + GetLaneByteOffset(Lane, IndexBits);
+ APInt ExpectedByteOffset = StartByteOffset + Lane * ElementSizeBytes;
if (SourceByteOffset != ExpectedByteOffset)
return false;
}
More information about the llvm-commits
mailing list