[llvm] [VectorCombine] Fold contiguous loads into a single vector load (PR #185736)

via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 22 06:54:02 PDT 2026


https://github.com/ParkHanbum updated https://github.com/llvm/llvm-project/pull/185736

>From 8d7f500001b4fbfd11ffb53619ebffee8f05ef93 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/24] 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 833df13b0346be0ba7413939eca23bbe9af65dca 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/24] [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 b3a545f388d17..665bb3d574904 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -164,6 +164,7 @@ class VectorCombine {
   bool shrinkLoadForShuffles(Instruction &I);
   bool shrinkPhiOfShuffles(Instruction &I);
   bool foldDeinterleaveInterleavePair(Instruction &I);
+  bool foldContiguousLoads(Instruction &I);
 
   void replaceValue(Instruction &Old, Value &New, bool Erase = true) {
     LLVM_DEBUG(dbgs() << "VC: Replacing: " << Old << '\n');
@@ -6724,6 +6725,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() {
@@ -6831,6 +6917,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 f9fb1cdbcbc1e84735830af952b63d8f5471703b 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/24] 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 665bb3d574904..3583dcef6af98 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6746,11 +6746,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 f730f765a8cf9d42830e4b2de6a0af0ad33bce27 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/24] 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 77d3fcc0478b893984f55b0cc71f8661230741ed 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/24] 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 923129ae1384533fc381553ca549e40c17b144bd 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/24] 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 3672cce528a07624f899f902eb07b651160b58a2 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/24] 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 3583dcef6af98..5789c47d5e499 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6761,6 +6761,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 9c9db39e90b50c77cc3a5f02565ed5149a5587ef 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/24] 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 0ba4f881d5d692f28d885de0ec31be0ad82839c0 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/24] 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 5789c47d5e499..6a63002ceb4c3 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6738,9 +6738,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;
@@ -6785,7 +6792,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;
     }
 
@@ -6800,20 +6814,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 fe80da693387a9b18740e4b6fb5a77151190bc24 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/24] 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 3ecdf8e97e61f2c284136391e2d4e88d8cd3ef2b 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/24] 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 3b7a6103c573d0085fe169a24c342c96bcd53153 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/24] 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 6a63002ceb4c3..11888d63799dc 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -32,6 +32,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/IR/ProfDataUtils.h"
 #include "llvm/Support/CommandLine.h"
@@ -6807,15 +6808,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
@@ -6824,7 +6838,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 cdaf9346cf46f6daad27693b4c7f45b903a9e7ff 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/24] 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 11888d63799dc..7797aa0402aec 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6726,14 +6726,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())
@@ -6747,13 +6775,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;
@@ -6776,37 +6806,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);
@@ -6818,12 +6854,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);
@@ -6838,9 +6877,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 00f51e79950e29a9387f506c066560df10730af7 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/24] 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 7797aa0402aec..0e1a72f39ac2a 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6726,6 +6726,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.
 ///
@@ -6780,6 +6800,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
@@ -6822,6 +6843,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
@@ -6879,7 +6906,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 696370db6f2a7a18cf43336aeb22c702d4270f19 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/24] 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 0e1a72f39ac2a..fec78b9715438 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6791,16 +6791,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
@@ -6827,22 +6833,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.
@@ -6855,13 +6860,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;
     }
 
@@ -6881,13 +6885,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 476d9deaa9de8ec1cbda5ab04fa6a2b922fcb08f 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/24] 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 fec78b9715438..39b46b3ab31c0 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6877,7 +6877,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 6d6c69e7bbd27a436e3b5e2bebf856bb052b8cb1 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/24] 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 64b015e1920abf9c733ba1426af4d3501b1276a4 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/24] 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 9ff0ce6e5fda81f02fce4b7cdee0f220cf5573d1 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/24] 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 39b46b3ab31c0..6db205fc04370 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6791,10 +6791,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 6e9f4215e4dd1491f3f6a63b5168e24fb8d4c731 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/24] 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 6db205fc04370..8ad5aae078d43 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6797,13 +6797,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
@@ -6839,7 +6832,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;
@@ -6860,8 +6853,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;
     }

>From 2c3396d33eb564e9b54998cce55b2b029c45b447 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Thu, 23 Jul 2026 17:13:50 +0900
Subject: [PATCH 21/24] Drop GEP no-wrap flag preservation

---
 .../Transforms/Vectorize/VectorCombine.cpp    | 30 +---------
 .../X86/fold-contiguous-loads.ll              | 57 +++----------------
 2 files changed, 8 insertions(+), 79 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 8ad5aae078d43..70684872f6a22 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6726,26 +6726,6 @@ 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.
 ///
@@ -6796,7 +6776,6 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
   Value *CommonBase = nullptr;
   APInt StartByteOffset(1, 0), FirstLoadByteOffset(1, 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
@@ -6838,12 +6817,6 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
       StartByteOffset = SourceByteOffset;
       FirstLI = LI;
       FirstLoadByteOffset = LoadByteOffsetAP;
-      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
@@ -6902,8 +6875,7 @@ 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, "", NewGEPFlags);
+  Value *NewBasePtr = Builder.CreatePtrAdd(CommonBase, StartByteOffsetValue);
   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 ff8dcf4d479f8..8b8ec62c76628 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 inbounds i8, ptr [[ARG0]], i64 48
+; 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]]
 ;
@@ -32,7 +32,7 @@ define <2 x float> @large_offset_without_signed_overflow(ptr %p) {
 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]]
 ;
@@ -46,7 +46,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 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 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(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]]
 ;
@@ -86,7 +86,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]]
 ;
@@ -545,7 +545,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 inbounds i8, ptr [[P]], i64 48
+; 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]]
 ;
@@ -571,49 +571,6 @@ define <2 x i1> @drop_load_metadata_for_multiple_attributed_loads(ptr %p) {
   ret <2 x i1> %cmp
 }
 
-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 nusw 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 nusw 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:%.*]]) {

>From 5f60fe037bbefa1b28595727171526646368681e Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Thu, 23 Jul 2026 17:25:05 +0900
Subject: [PATCH 22/24] Preserve common load value metadata

---
 .../Transforms/Vectorize/VectorCombine.cpp    | 18 +++++
 .../X86/fold-contiguous-loads.ll              | 77 +++++++++++++++++--
 2 files changed, 88 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 70684872f6a22..2c4d6b8031411 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6880,6 +6880,24 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
   if (Loads.size() == 1)
     copyMetadataForLoad(*NewLoad, *FirstLI);
 
+  // Preserve value restrictions that hold for every source load. A range
+  // must cover every source range, while noundef and excluded FP classes
+  // must be common to all sources.
+  MDNode *NoundefMD = FirstLI->getMetadata(LLVMContext::MD_noundef);
+  MDNode *RangeMD = FirstLI->getMetadata(LLVMContext::MD_range);
+  MDNode *NoFPClassMD = FirstLI->getMetadata(LLVMContext::MD_nofpclass);
+  for (LoadInst *LI : Loads) {
+    if (!LI->hasMetadata(LLVMContext::MD_noundef))
+      NoundefMD = nullptr;
+    RangeMD = MDNode::getMostGenericRange(
+        RangeMD, LI->getMetadata(LLVMContext::MD_range));
+    NoFPClassMD = MDNode::getMostGenericNoFPClass(
+        NoFPClassMD, LI->getMetadata(LLVMContext::MD_nofpclass));
+  }
+  NewLoad->setMetadata(LLVMContext::MD_noundef, NoundefMD);
+  NewLoad->setMetadata(LLVMContext::MD_range, RangeMD);
+  NewLoad->setMetadata(LLVMContext::MD_nofpclass, NoFPClassMD);
+
   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 8b8ec62c76628..403d5c430d0bc 100644
--- a/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/fold-contiguous-loads.ll
@@ -555,22 +555,77 @@ define <2 x float> @preserve_load_metadata_for_single_attributed_load(ptr %p) {
   ret <2 x float> %r
 }
 
-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(
+define <3 x i32> @drop_load_metadata_for_multiple_loads(ptr %p) {
+; CHECK-LABEL: define <3 x i32> @drop_load_metadata_for_multiple_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, !invariant.load !6
+  %p1 = getelementptr i8, ptr %p, i64 8
+  %l1 = load <2 x i32>, ptr %p1, 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 i1> @merge_range_and_noundef_metadata(ptr %p) {
+; CHECK-LABEL: define <2 x i1> @merge_range_and_noundef_metadata(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 1
+; CHECK-NEXT:    [[R:%.*]] = load <2 x i8>, ptr [[TMP1]], align 1, !range [[RNG6:![0-9]+]], !noundef [[META7:![0-9]+]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult <2 x i8> [[R]], splat (i8 2)
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %l0 = load <2 x i8>, ptr %p, align 2, !range !7, !noundef !6
+  %p1 = getelementptr i8, ptr %p, i64 2
+  %l1 = load <2 x i8>, ptr %p1, align 2, !range !8, !noundef !6
+  %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 <2 x i1> @drop_noncommon_range_and_noundef_metadata(ptr %p) {
+; CHECK-LABEL: define <2 x i1> @drop_noncommon_range_and_noundef_metadata(
 ; CHECK-SAME: ptr [[P:%.*]]) {
 ; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr i8, ptr [[P]], i64 1
-; CHECK-NEXT:    [[R:%.*]] = load <2 x i8>, ptr [[TMP1]], align 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 i8>, ptr %p, align 2, !range !6
+  %l0 = load <2 x i8>, ptr %p, align 2, !range !7, !noundef !6
   %p1 = getelementptr i8, ptr %p, i64 2
-  %l1 = load <2 x i8>, ptr %p1, align 2, !range !7
+  %l1 = load <2 x i8>, ptr %p1, align 2, !range !9
   %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 float> @intersect_nofpclass_metadata(ptr %p) {
+; CHECK-LABEL: define <3 x float> @intersect_nofpclass_metadata(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = load <3 x float>, ptr [[P]], align 8, !nofpclass [[META8:![0-9]+]]
+; CHECK-NEXT:    ret <3 x float> [[R]]
+;
+  %l0 = load <2 x float>, ptr %p, align 8, !nofpclass !10
+  %p1 = getelementptr i8, ptr %p, i64 8
+  %l1 = load <2 x float>, ptr %p1, align 8, !nofpclass !11
+  %r = shufflevector <2 x float> %l0, <2 x float> %l1, <3 x i32> <i32 0, i32 1, i32 2>
+  ret <3 x float> %r
+}
+
+define <3 x float> @drop_noncommon_nofpclass_metadata(ptr %p) {
+; CHECK-LABEL: define <3 x float> @drop_noncommon_nofpclass_metadata(
+; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = load <3 x float>, ptr [[P]], align 8
+; CHECK-NEXT:    ret <3 x float> [[R]]
+;
+  %l0 = load <2 x float>, ptr %p, align 8, !nofpclass !11
+  %p1 = getelementptr i8, ptr %p, i64 8
+  %l1 = load <2 x float>, ptr %p1, align 8, !nofpclass !12
+  %r = shufflevector <2 x float> %l0, <2 x float> %l1, <3 x i32> <i32 0, i32 1, i32 2>
+  ret <3 x float> %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:%.*]]) {
@@ -601,8 +656,13 @@ other:
 !3 = !{!4}
 !4 = distinct !{!4, !5}
 !5 = distinct !{!5}
-!6 = !{i8 0, i8 2}
-!7 = !{i8 2, i8 4}
+!6 = !{}
+!7 = !{i8 0, i8 2}
+!8 = !{i8 2, i8 4}
+!9 = !{i8 2, i8 0}
+!10 = !{i32 519}
+!11 = !{i32 3}
+!12 = !{i32 516}
 ;.
 ; CHECK: [[FLOAT_TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0}
 ; CHECK: [[META1]] = !{!"float", [[META2:![0-9]+]], i64 0}
@@ -610,4 +670,7 @@ other:
 ; CHECK: [[META3]] = !{[[META4:![0-9]+]]}
 ; CHECK: [[META4]] = distinct !{[[META4]], [[META5:![0-9]+]]}
 ; CHECK: [[META5]] = distinct !{[[META5]]}
+; CHECK: [[RNG6]] = !{i8 0, i8 4}
+; CHECK: [[META7]] = !{}
+; CHECK: [[META8]] = !{i32 3}
 ;.

>From 0e773ab175c7be2ae67ab8bffb1e68eb5de39701 Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Fri, 24 Jul 2026 18:23:23 +0900
Subject: [PATCH 23/24] formatting

---
 llvm/lib/Transforms/Vectorize/VectorCombine.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
index 2c4d6b8031411..4108f9ea184eb 100644
--- a/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
+++ b/llvm/lib/Transforms/Vectorize/VectorCombine.cpp
@@ -6810,8 +6810,7 @@ bool VectorCombine::foldContiguousLoads(Instruction &I) {
     unsigned IndexBits = DL->getIndexTypeSizeInBits(Base->getType());
     APInt LoadByteOffsetAP(IndexBits, LoadByteOffset, /*isSigned=*/true);
     APInt SourceByteOffset =
-        LoadByteOffsetAP +
-        static_cast<uint64_t>(IL.second) * ElementSizeBytes;
+        LoadByteOffsetAP + static_cast<uint64_t>(IL.second) * ElementSizeBytes;
     if (Lane == 0) {
       CommonBase = Base;
       StartByteOffset = SourceByteOffset;

>From 373f491ffcd3c78934fee763f5439141e64964ae Mon Sep 17 00:00:00 2001
From: hanbeom <kese111 at gmail.com>
Date: Sat, 22 Aug 2026 22:53:35 +0900
Subject: [PATCH 24/24] updated testcase after conflic resolved

---
 .../Transforms/VectorCombine/X86/load-widening.ll   | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/llvm/test/Transforms/VectorCombine/X86/load-widening.ll b/llvm/test/Transforms/VectorCombine/X86/load-widening.ll
index 0e38b873a0c8d..e5bf7aa3da520 100644
--- a/llvm/test/Transforms/VectorCombine/X86/load-widening.ll
+++ b/llvm/test/Transforms/VectorCombine/X86/load-widening.ll
@@ -255,15 +255,10 @@ define <2 x i4> @vec_with_2elts_128bits_i4(ptr align 16 dereferenceable(16) %p)
 ; Shrinking this to <9 x double> would change the legalized type.
 
 define <8 x double> @load_v16f64(ptr %p) {
-; SSE-LABEL: @load_v16f64(
-; SSE-NEXT:    [[TMP1:%.*]] = load <10 x double>, ptr [[P:%.*]], align 512
-; SSE-NEXT:    [[S:%.*]] = shufflevector <10 x double> [[TMP1]], <10 x double> poison, <8 x i32> <i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8>
-; SSE-NEXT:    ret <8 x double> [[S]]
-;
-; AVX-LABEL: @load_v16f64(
-; AVX-NEXT:    [[TMP1:%.*]] = load <12 x double>, ptr [[P:%.*]], align 512
-; AVX-NEXT:    [[S:%.*]] = shufflevector <12 x double> [[TMP1]], <12 x double> poison, <8 x i32> <i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8>
-; AVX-NEXT:    ret <8 x double> [[S]]
+; CHECK-LABEL: @load_v16f64(
+; CHECK-NEXT:    [[TMP1:%.*]] = getelementptr i8, ptr [[P:%.*]], i64 8
+; CHECK-NEXT:    [[S:%.*]] = load <8 x double>, ptr [[TMP1]], align 8
+; CHECK-NEXT:    ret <8 x double> [[S]]
 ;
   %l = load <16 x double>, ptr %p, align 512
   %s = shufflevector <16 x double> %l, <16 x double> poison, <8 x i32> <i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8>



More information about the llvm-commits mailing list