Bug type: crash
Component: SLPVectorizer
Bug location: llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp: BoUpSLP::analyzeConstantStrideCandidate
BoUpSLP::canVectorizeLoads
Issue title: SLP vectorizer crashes with assertions on
Labels: release:backport
llvm:SLPVectorizer
crash
Reported by: gbaraldi

This is the repro in godbold
https://godbolt.org/z/c3nzMcrGs

```llvm
target triple = "x86_64-unknown-linux-gnu"

define void @test(i64 %arg0, i64 %arg1) {
entry:
  %init = add i64 %arg0, 1
  br label %loop

loop:
  %iv = phi i64 [ %init, %entry ], [ %iv.next, %reduce ]
  %counter = phi i64 [ 1, %entry ], [ %counter.next, %reduce ]
  %off0 = add i64 %iv, -4
  %off1 = add i64 %iv, -3
  %off2 = add i64 %iv, -2
  %off3 = add i64 %iv, -1
  %ptr = call ptr null(ptr null, ptr null)
  %idx0 = add i64 %arg1, %off0
  %idx0.scaled = shl i64 %idx0, 3
  %gep0 = getelementptr i8, ptr %ptr, i64 %idx0.scaled
  %gep0.off = getelementptr i8, ptr %gep0, i64 -8
  %load0 = load double, ptr %gep0.off, align 8
  %idx1 = add i64 %arg1, %off1
  %idx1.scaled = shl i64 %idx1, 3
  %gep1 = getelementptr i8, ptr %ptr, i64 %idx1.scaled
  %gep1.off = getelementptr i8, ptr %gep1, i64 -8
  %load1 = load double, ptr %gep1.off, align 8
  %idx2 = add i64 %arg1, %off2
  %idx2.scaled = shl i64 %idx2, 3
  %gep2 = getelementptr i8, ptr %ptr, i64 %idx2.scaled
  %gep2.off = getelementptr i8, ptr %gep2, i64 -8
  %load2 = load double, ptr %gep2.off, align 8
  %idx3 = add i64 %arg1, %off3
  %idx3.scaled = shl i64 %idx3, 3
  %gep3 = getelementptr i8, ptr %ptr, i64 %idx3.scaled
  %gep3.off = getelementptr i8, ptr %gep3, i64 -8
  %load3 = load double, ptr %gep3.off, align 8
  %idx4 = add i64 %arg1, %iv
  %idx4.scaled = shl i64 %idx4, 3
  %gep4 = getelementptr i8, ptr %ptr, i64 %idx4.scaled
  %gep4.off = getelementptr i8, ptr %gep4, i64 -8
  %load4 = load double, ptr %gep4.off, align 8
  %load5 = load double, ptr %gep4, align 8
  br label %reduce

dead:
  br label %reduce

reduce:
  %phi0 = phi double [ %load0, %loop ], [ 0.000000e+00, %dead ]
  %phi1 = phi double [ %load1, %loop ], [ 0.000000e+00, %dead ]
  %phi2 = phi double [ %load2, %loop ], [ 0.000000e+00, %dead ]
  %phi3 = phi double [ %load3, %loop ], [ 0.000000e+00, %dead ]
  %phi4 = phi double [ %load4, %loop ], [ 0.000000e+00, %dead ]
  %phi5 = phi double [ %load5, %loop ], [ 0.000000e+00, %dead ]
  %min0 = call double @llvm.minimum.f64(double 0.000000e+00, double %phi0)
  %min1 = call double @llvm.minimum.f64(double %min0, double %phi1)
  %min2 = call double @llvm.minimum.f64(double %min1, double %phi2)
  %min3 = call double @llvm.minimum.f64(double %min2, double %phi3)
  %min4 = call double @llvm.minimum.f64(double %min3, double %phi4)
  %min5 = call double @llvm.minimum.f64(double %min4, double %phi5)
  %min6 = call double @llvm.minimum.f64(double %min5, double 0.000000e+00)
  %counter.next = add i64 %counter, 1
  %iv.next = add i64 %counter, %init
  br label %loop
}

declare double @llvm.minimum.f64(double, double)

```

Comments: author: RKSimon
body: 22.x regression - tentatively tagging this for cherry picking 
author: gbaraldi
body: This broke between 18 and 20 (this was found in Julia when we did that bump) 
author: RKSimon
body: > This broke between 18 and 20 (this was found in Julia when we did that bump)

@alexey-bataev if it is this old, should we get it fixed in 22.x? is there a higher chance of it occurring with more recent slp features?
author: alexey-bataev
body: Potentially yes, so it would be good to port it to 22

Fix patch:
commit 72a20b8e29876106aa918d03ef95f27a6bf0fa0d
Author: Gabriel Baraldi <baraldigabriel@gmail.com>
Date:   Thu Jan 15 12:07:13 2026 -0300

    [SLPVectorizer] Check std::optional coming out of getPointersDiff (#175784)
    
    Fixes https://github.com/llvm/llvm-project/issues/175768
    There are other unchecked uses std::optional in this pass but I couldn't
    figure out a test that triggers them

diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 8611d4023866..b7ab056ef436 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -7082,12 +7082,17 @@ bool BoUpSLP::analyzeConstantStrideCandidate(
     Value *Ptr0, Value *PtrN, StridedPtrInfo &SPtrInfo) const {
   const size_t Sz = PointerOps.size();
   SmallVector<int64_t> SortedOffsetsFromBase(Sz);
-  // Go through `PointerOps` in sorted order and record offsets from `Ptr0`.
+  // Go through `PointerOps` in sorted order and record offsets from
+  // PointerOps[0]. We use PointerOps[0] rather than Ptr0 because
+  // sortPtrAccesses only validates getPointersDiff for pairs relative to
+  // PointerOps[0]. This is safe since only offset differences are used below.
   for (unsigned I : seq<unsigned>(Sz)) {
     Value *Ptr =
         SortedIndices.empty() ? PointerOps[I] : PointerOps[SortedIndices[I]];
-    SortedOffsetsFromBase[I] =
-        *getPointersDiff(ScalarTy, Ptr0, ScalarTy, Ptr, *DL, *SE);
+    std::optional<int64_t> Offset =
+        getPointersDiff(ScalarTy, PointerOps[0], ScalarTy, Ptr, *DL, *SE);
+    assert(Offset && "sortPtrAccesses should have validated this pointer");
+    SortedOffsetsFromBase[I] = *Offset;
   }
 
   // The code below checks that `SortedOffsetsFromBase` looks as follows:
@@ -7436,10 +7441,18 @@ BoUpSLP::LoadsState BoUpSLP::canVectorizeLoads(
       Ptr0 = PointerOps[Order.front()];
       PtrN = PointerOps[Order.back()];
     }
-    std::optional<int64_t> Diff =
-        getPointersDiff(ScalarTy, Ptr0, ScalarTy, PtrN, *DL, *SE);
+    // sortPtrAccesses validates getPointersDiff for all pointers relative to
+    // PointerOps[0], so compute the span using PointerOps[0] as intermediate:
+    //   Diff = offset(PtrN) - offset(Ptr0) relative to PointerOps[0]
+    std::optional<int64_t> Diff0 =
+        getPointersDiff(ScalarTy, PointerOps[0], ScalarTy, Ptr0, *DL, *SE);
+    std::optional<int64_t> DiffN =
+        getPointersDiff(ScalarTy, PointerOps[0], ScalarTy, PtrN, *DL, *SE);
+    assert(Diff0 && DiffN &&
+           "sortPtrAccesses should have validated these pointers");
+    int64_t Diff = *DiffN - *Diff0;
     // Check that the sorted loads are consecutive.
-    if (static_cast<uint64_t>(*Diff) == Sz - 1)
+    if (static_cast<uint64_t>(Diff) == Sz - 1)
       return LoadsState::Vectorize;
     if (isMaskedLoadCompress(VL, PointerOps, Order, *TTI, *DL, *SE, *AC, *DT,
                              *TLI, [&](Value *V) {
@@ -7451,7 +7464,7 @@ BoUpSLP::LoadsState BoUpSLP::canVectorizeLoads(
         cast<LoadInst>(Order.empty() ? VL.front() : VL[Order.front()])
             ->getAlign();
     if (analyzeConstantStrideCandidate(PointerOps, ScalarTy, Alignment, Order,
-                                       *Diff, Ptr0, PtrN, SPtrInfo))
+                                       Diff, Ptr0, PtrN, SPtrInfo))
       return LoadsState::StridedVectorize;
   }
   if (!TTI->isLegalMaskedGather(VecTy, CommonAlignment) ||


Regression tests:
file: llvm/test/Transforms/SLPVectorizer/X86/crash_getpointersdiff-nullopt.ll
commands: opt < %s -passes=slp-vectorizer -S -mtriple=x86_64-unknown-linux-gnu
tests: test_name: <module>
test_body: 
; Test that SLP vectorizer doesn't crash when getPointersDiff returns
; std::nullopt for pointers that can't be compared.

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

define void @test(i64 %arg0, i64 %arg1) {
;
entry:
  %init = add i64 %arg0, 1
  br label %loop

loop:
  %iv = phi i64 [ %init, %entry ], [ %iv.next, %reduce ]
  %counter = phi i64 [ 1, %entry ], [ %counter.next, %reduce ]
  %off0 = add i64 %iv, -4
  %off1 = add i64 %iv, -3
  %off2 = add i64 %iv, -2
  %off3 = add i64 %iv, -1
  %ptr = call ptr null(ptr null, ptr null)
  %idx0 = add i64 %arg1, %off0
  %idx0.scaled = shl i64 %idx0, 3
  %gep0 = getelementptr i8, ptr %ptr, i64 %idx0.scaled
  %gep0.off = getelementptr i8, ptr %gep0, i64 -8
  %load0 = load double, ptr %gep0.off, align 8
  %idx1 = add i64 %arg1, %off1
  %idx1.scaled = shl i64 %idx1, 3
  %gep1 = getelementptr i8, ptr %ptr, i64 %idx1.scaled
  %gep1.off = getelementptr i8, ptr %gep1, i64 -8
  %load1 = load double, ptr %gep1.off, align 8
  %idx2 = add i64 %arg1, %off2
  %idx2.scaled = shl i64 %idx2, 3
  %gep2 = getelementptr i8, ptr %ptr, i64 %idx2.scaled
  %gep2.off = getelementptr i8, ptr %gep2, i64 -8
  %load2 = load double, ptr %gep2.off, align 8
  %idx3 = add i64 %arg1, %off3
  %idx3.scaled = shl i64 %idx3, 3
  %gep3 = getelementptr i8, ptr %ptr, i64 %idx3.scaled
  %gep3.off = getelementptr i8, ptr %gep3, i64 -8
  %load3 = load double, ptr %gep3.off, align 8
  %idx4 = add i64 %arg1, %iv
  %idx4.scaled = shl i64 %idx4, 3
  %gep4 = getelementptr i8, ptr %ptr, i64 %idx4.scaled
  %gep4.off = getelementptr i8, ptr %gep4, i64 -8
  %load4 = load double, ptr %gep4.off, align 8
  %load5 = load double, ptr %gep4, align 8
  br label %reduce

dead:
  br label %reduce

reduce:
  %phi0 = phi double [ %load0, %loop ], [ 0.000000e+00, %dead ]
  %phi1 = phi double [ %load1, %loop ], [ 0.000000e+00, %dead ]
  %phi2 = phi double [ %load2, %loop ], [ 0.000000e+00, %dead ]
  %phi3 = phi double [ %load3, %loop ], [ 0.000000e+00, %dead ]
  %phi4 = phi double [ %load4, %loop ], [ 0.000000e+00, %dead ]
  %phi5 = phi double [ %load5, %loop ], [ 0.000000e+00, %dead ]
  %min0 = call double @llvm.minimum.f64(double 0.000000e+00, double %phi0)
  %min1 = call double @llvm.minimum.f64(double %min0, double %phi1)
  %min2 = call double @llvm.minimum.f64(double %min1, double %phi2)
  %min3 = call double @llvm.minimum.f64(double %min2, double %phi3)
  %min4 = call double @llvm.minimum.f64(double %min3, double %phi4)
  %min5 = call double @llvm.minimum.f64(double %min4, double %phi5)
  %min6 = call double @llvm.minimum.f64(double %min5, double 0.000000e+00)
  %counter.next = add i64 %counter, 1
  %iv.next = add i64 %counter, %init
  br label %loop
}

declare double @llvm.minimum.f64(double, double)