[llvm] [IVDescriptors] Check for null SE before dereference in getMinMaxRecurrence. (PR #195798)

Craig Topper via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 11:56:47 PDT 2026


https://github.com/topperc updated https://github.com/llvm/llvm-project/pull/195798

>From 84eff50295d8058699a26e125040ff1c182c03af Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Mon, 4 May 2026 23:23:51 -0700
Subject: [PATCH 1/2] [IVDescriptors] Check for null SE before deference in
 getMinMaxRecurrence.

Some callers don't pass SCEV.
---
 llvm/lib/Analysis/IVDescriptors.cpp           |  2 +
 llvm/unittests/Analysis/IVDescriptorsTest.cpp | 45 +++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/llvm/lib/Analysis/IVDescriptors.cpp b/llvm/lib/Analysis/IVDescriptors.cpp
index 96d30fd1b1415..7549e14366d2c 100644
--- a/llvm/lib/Analysis/IVDescriptors.cpp
+++ b/llvm/lib/Analysis/IVDescriptors.cpp
@@ -393,6 +393,8 @@ static RecurrenceDescriptor getMinMaxRecurrence(PHINode *Phi, Loop *TheLoop,
   StoreInst *IntermediateStore = nullptr;
   const SCEV *StorePtrSCEV = nullptr;
   for (StoreInst *SI : Stores) {
+    if (!SE)
+      return {};
     const SCEV *Ptr = SE->getSCEV(SI->getPointerOperand());
     if (!SE->isLoopInvariant(Ptr, TheLoop) ||
         (StorePtrSCEV && StorePtrSCEV != Ptr))
diff --git a/llvm/unittests/Analysis/IVDescriptorsTest.cpp b/llvm/unittests/Analysis/IVDescriptorsTest.cpp
index 400e90d2e52bf..a56f9800fed76 100644
--- a/llvm/unittests/Analysis/IVDescriptorsTest.cpp
+++ b/llvm/unittests/Analysis/IVDescriptorsTest.cpp
@@ -338,3 +338,48 @@ TEST(IVDescriptorsTest, UnsupportedFindLastPhi) {
                          EXPECT_FALSE(IsRdxPhi);
                        });
 }
+
+// Make sure isReductionPHI doesn't crash when SE is not passed to it.
+TEST(IVDescriptorsTest, InvariantStoreNoSCEV) {
+  // Parse the module.
+  LLVMContext Context;
+
+  std::unique_ptr<Module> M = parseIR(Context, R"(
+    define void @smax_with_invariant_store_user(ptr noalias %src, ptr %dst, i64 %n) {
+    entry:
+      br label %loop
+
+    loop:
+      %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
+      %max = phi i32 [ 0, %entry ], [ %max.next, %loop ]
+      %gep.src = getelementptr inbounds i32, ptr %src, i64 %iv
+      %l = load i32, ptr %gep.src, align 4
+      %max.next = tail call i32 @llvm.smax.i32(i32 %max, i32 %l)
+      store i32 %max.next, ptr %dst, align 4
+      %iv.next = add i64 %iv, 1
+      %ec = icmp eq i64 %iv, %n
+      br i1 %ec, label %exit, label %loop
+
+    exit:
+      ret void
+    })");
+
+  runWithLoopInfoAndSE(*M, "smax_with_invariant_store_user",
+                       [&](Function &F, LoopInfo &LI, ScalarEvolution &SE) {
+                         Function::iterator FI = F.begin();
+                         // First basic block is entry - skip it.
+                         BasicBlock *Header = &*(++FI);
+                         assert(Header->getName() == "loop");
+                         Loop *L = LI.getLoopFor(Header);
+                         EXPECT_NE(L, nullptr);
+                         BasicBlock::iterator BBI = Header->begin();
+                         ++BBI;
+                         PHINode *Phi = dyn_cast<PHINode>(&*BBI);
+                         EXPECT_NE(Phi, nullptr);
+                         EXPECT_EQ(Phi->getName(), "max");
+                         RecurrenceDescriptor Rdx;
+                         bool IsRdxPhi =
+                             RecurrenceDescriptor::isReductionPHI(Phi, L, Rdx);
+                         EXPECT_FALSE(IsRdxPhi);
+                       });
+}

>From 40e536f5d8a324e72c2f26ba83d43ac4bc513a3f Mon Sep 17 00:00:00 2001
From: Craig Topper <craig.topper at sifive.com>
Date: Tue, 5 May 2026 11:56:25 -0700
Subject: [PATCH 2/2] fixup! Add loop interchange test

---
 .../LoopInterchange/reduction-crash.ll        | 60 +++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 llvm/test/Transforms/LoopInterchange/reduction-crash.ll

diff --git a/llvm/test/Transforms/LoopInterchange/reduction-crash.ll b/llvm/test/Transforms/LoopInterchange/reduction-crash.ll
new file mode 100644
index 0000000000000..fcf218a146e38
--- /dev/null
+++ b/llvm/test/Transforms/LoopInterchange/reduction-crash.ll
@@ -0,0 +1,60 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=loop-interchange -S | FileCheck %s
+
+; This test used to crash due to a dereference of a null ScalarEvolution pointer
+; inside isReductionPHI.
+define void @reduction_smax_store(ptr %A) {
+; CHECK-LABEL: define void @reduction_smax_store(
+; CHECK-SAME: ptr [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[FOR_I_HEADER:.*]]
+; CHECK:       [[FOR_I_HEADER]]:
+; CHECK-NEXT:    [[I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[I_INC:%.*]], %[[FOR_I_LATCH:.*]] ]
+; CHECK-NEXT:    [[MAX_I:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[MAX_I_LCSSA:%.*]], %[[FOR_I_LATCH]] ]
+; CHECK-NEXT:    br label %[[FOR_J:.*]]
+; CHECK:       [[FOR_J]]:
+; CHECK-NEXT:    [[J:%.*]] = phi i32 [ 0, %[[FOR_I_HEADER]] ], [ [[J_INC:%.*]], %[[FOR_J]] ]
+; CHECK-NEXT:    [[MAX_J:%.*]] = phi i32 [ [[MAX_I]], %[[FOR_I_HEADER]] ], [ [[MAX_J_NEXT:%.*]], %[[FOR_J]] ]
+; CHECK-NEXT:    [[IDX:%.*]] = getelementptr inbounds [2 x [2 x i32]], ptr [[A]], i32 0, i32 [[J]], i32 [[I]]
+; CHECK-NEXT:    [[A:%.*]] = load i32, ptr [[IDX]], align 4
+; CHECK-NEXT:    [[MAX_J_NEXT]] = tail call i32 @llvm.smax.i32(i32 [[MAX_J]], i32 [[A]])
+; CHECK-NEXT:    store i32 [[MAX_J_NEXT]], ptr [[IDX]], align 4
+; CHECK-NEXT:    [[J_INC]] = add i32 [[J]], 1
+; CHECK-NEXT:    [[CMP_J:%.*]] = icmp slt i32 [[J_INC]], 2
+; CHECK-NEXT:    br i1 [[CMP_J]], label %[[FOR_J]], label %[[FOR_I_LATCH]]
+; CHECK:       [[FOR_I_LATCH]]:
+; CHECK-NEXT:    [[MAX_I_LCSSA]] = phi i32 [ [[MAX_J_NEXT]], %[[FOR_J]] ]
+; CHECK-NEXT:    [[I_INC]] = add i32 [[I]], 1
+; CHECK-NEXT:    [[CMP_I:%.*]] = icmp slt i32 [[I_INC]], 2
+; CHECK-NEXT:    br i1 [[CMP_I]], label %[[FOR_I_HEADER]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %for.i.header
+
+for.i.header:
+  %i = phi i32 [ 0, %entry ], [ %i.inc, %for.i.latch ]
+  %max.i = phi i32 [ 0, %entry ], [ %max.i.lcssa, %for.i.latch ]
+  br label %for.j
+
+for.j:
+  %j = phi i32 [ 0, %for.i.header ], [ %j.inc, %for.j ]
+  %max.j = phi i32 [ %max.i, %for.i.header ], [ %max.j.next, %for.j ]
+  %idx = getelementptr inbounds [2 x [2 x i32]], ptr %A, i32 0, i32 %j, i32 %i
+  %a = load i32, ptr %idx, align 4
+  %max.j.next = tail call i32 @llvm.smax.i32(i32 %max.j, i32 %a)
+  store i32 %max.j.next, ptr %idx
+  %j.inc = add i32 %j, 1
+  %cmp.j = icmp slt i32 %j.inc, 2
+  br i1 %cmp.j, label %for.j, label %for.i.latch
+
+for.i.latch:
+  %max.i.lcssa = phi i32 [ %max.j.next, %for.j ]
+  %i.inc = add i32 %i, 1
+  %cmp.i = icmp slt i32 %i.inc, 2
+  br i1 %cmp.i, label %for.i.header, label %exit
+
+exit:
+  ret void
+}



More information about the llvm-commits mailing list