Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/LoopVectorize.cpp: LoopVectorizationCostModel::getDivRemSpeculationCost
planContainsAdditionalSimplifications
Issue title: [LV] " VPlan cost model and legacy cost model disagreed" regression on Neoverse-V2
Labels: vectorizers
crash
Reported by: mcinally

Similar to #160345, which was for Neoverse-V1...

We're seeing a good number of downstream ICEs from a change around 2025-09-22. Here is one reduced issue from Neoverse-V2:

```
scrubbed:$ cat test.ll
target triple = "aarch64-unknown-linux-gnu"

define void @foo(ptr %0) local_unnamed_addr #0 {
  br i1 poison, label %.lr.ph, label %._crit_edge

.lr.ph:                                           ; preds = %1
  br label %2

2:                                                ; preds = %11, %.lr.ph
  %3 = phi i64 [ poison, %.lr.ph ], [ %13, %11 ]
  %4 = phi i32 [ poison, %.lr.ph ], [ %12, %11 ]
  %5 = srem i32 %4, poison
  %6 = icmp eq i32 %5, 0
  br i1 %6, label %7, label %11

7:                                                ; preds = %2
  %8 = sdiv i32 %4, poison
  %9 = sext i32 %8 to i64
  %10 = getelementptr i32, ptr %0, i64 %9
  store i32 1, ptr %10, align 4
  br label %11

11:                                               ; preds = %7, %2
  %12 = add nsw i32 %4, 1
  %13 = add nsw i64 %3, -1
  %14 = icmp sgt i64 %3, 1
  br i1 %14, label %2, label %._crit_edge

._crit_edge:                                      ; preds = %11, %1
  ret void
}

attributes #0 = { "target-cpu"="neoverse-v2" }
scrubbed:$ opt --passes=loop-vectorize -S test.ll 
opt: scrubbed/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp:7090: llvm::VectorizationFactor llvm::LoopVectorizationPlanner::computeBestVF(): Assertion `(BestFactor.Width == LegacyVF.Width || BestPlan.hasEarlyExit() || planContainsAdditionalSimplifications(getPlanFor(BestFactor.Width), CostCtx, OrigLoop, BestFactor.Width) || planContainsAdditionalSimplifications( getPlanFor(LegacyVF.Width), CostCtx, OrigLoop, LegacyVF.Width)) && " VPlan cost model and legacy cost model disagreed"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
Stack dump:
0.	Program arguments: opt --passes=loop-vectorize -S test.ll
1.	Running pass "function(loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>)" on module "test.ll"
2.	Running pass "loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>" on function "foo"
 #0 0x0000000001bf3e10 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (scrubbed/llvm-3986/bin/opt+0x1bf3e10)
 #1 0x0000000001bf0f38 llvm::sys::RunSignalHandlers() (scrubbed/llvm-3986/bin/opt+0x1bf0f38)
 #2 0x0000000001bf10bc SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #3 0x00004003685c08dc (linux-vdso.so.1+0x8dc)
 #4 0x000040036887f200 __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
 #5 0x000040036883a67c gsignal ./signal/../sysdeps/posix/raise.c:27:6
 #6 0x0000400368827130 abort ./stdlib/abort.c:81:7
 #7 0x0000400368833fd0 __assert_fail_base ./assert/assert.c:89:7
 #8 0x0000400368834040 __assert_perror_fail ./assert/assert-perr.c:31:1
 #9 0x0000000003990f98 llvm::LoopVectorizationPlanner::computeBestVF() (scrubbed/llvm-3986/bin/opt+0x3990f98)
#10 0x000000000399c054 llvm::LoopVectorizePass::processLoop(llvm::Loop*) (scrubbed/llvm-3986/bin/opt+0x399c054)
#11 0x000000000399e448 llvm::LoopVectorizePass::runImpl(llvm::Function&) (scrubbed/llvm-3986/bin/opt+0x399e448)
#12 0x000000000399ea08 llvm::LoopVectorizePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (scrubbed/llvm-3986/bin/opt+0x399ea08)
#13 0x00000000034b2db0 llvm::detail::PassModel<llvm::Function, llvm::LoopVectorizePass, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) PassBuilder.cpp:0:0
#14 0x0000000001e9e8e4 llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (scrubbed/llvm-3986/bin/opt+0x1e9e8e4)
#15 0x0000000002dd74e0 llvm::detail::PassModel<llvm::Function, llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) NVPTXTargetMachine.cpp:0:0
#16 0x0000000001e9d0d0 llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (scrubbed/llvm-3986/bin/opt+0x1e9d0d0)
#17 0x0000000002dd7490 llvm::detail::PassModel<llvm::Module, llvm::ModuleToFunctionPassAdaptor, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) NVPTXTargetMachine.cpp:0:0
#18 0x0000000001e9cb3c llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (scrubbed/llvm-3986/bin/opt+0x1e9cb3c)
#19 0x00000000033602a0 llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef<llvm::PassPlugin>, llvm::ArrayRef<std::function<void (llvm::PassBuilder&)>>, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool, bool) (scrubbed/llvm-3986/bin/opt+0x33602a0)
#20 0x0000000001bd66f4 optMain (scrubbed/llvm-3986/bin/opt+0x1bd66f4)
#21 0x00004003688273fc __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:74:3
#22 0x00004003688274cc call_init ./csu/../csu/libc-start.c:128:20
#23 0x00004003688274cc __libc_start_main ./csu/../csu/libc-start.c:379:5
#24 0x0000000001bcb38c _start (scrubbed/llvm-3986/bin/opt+0x1bcb38c)
Aborted
``` 

Comments: author: mcinally
body: @fhahn for visibility.

Fix patch:
commit 88aab08ae5682a21edef71b814e5ebc05e7a9450
Author: Florian Hahn <flo@fhahn.com>
Date:   Tue Sep 23 21:54:02 2025 +0100

    [LV] Check for hoisted safe-div selects in planContainsAdditionalSimp.
    
    In some cases, safe-divisor selects can be hoisted out of the vector
    loop. Catching all cases in the legacy cost model isn't possible, in
    particular checking if all conditions guarding a division are loop
    invariant.
    
    Instead, check in planContainsAdditionalSimplifications if there are any
    hoisted safe-divisor selects. If so, don't compare to the more
    inaccurate legacy cost model.
    
    Fixes https://github.com/llvm/llvm-project/issues/160354.
    Fixes https://github.com/llvm/llvm-project/issues/160356.

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index ca092dcfcb49..30fcc9b7680e 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -2907,15 +2907,12 @@ LoopVectorizationCostModel::getDivRemSpeculationCost(Instruction *I,
 
   InstructionCost SafeDivisorCost = 0;
   auto *VecTy = toVectorTy(I->getType(), VF);
-  auto *DivisorI = dyn_cast<Instruction>(I->getOperand(1));
-  if (DivisorI && !Legal->isInvariant(DivisorI)) {
-    // The cost of the select guard to ensure all lanes are well defined
-    // after we speculate above any internal control flow.
-    SafeDivisorCost +=
-        TTI.getCmpSelInstrCost(Instruction::Select, VecTy,
-                               toVectorTy(Type::getInt1Ty(I->getContext()), VF),
-                               CmpInst::BAD_ICMP_PREDICATE, CostKind);
-  }
+  // The cost of the select guard to ensure all lanes are well defined
+  // after we speculate above any internal control flow.
+  SafeDivisorCost +=
+      TTI.getCmpSelInstrCost(Instruction::Select, VecTy,
+                             toVectorTy(Type::getInt1Ty(I->getContext()), VF),
+                             CmpInst::BAD_ICMP_PREDICATE, CostKind);
 
   SmallVector<const Value *, 4> Operands(I->operand_values());
   SafeDivisorCost += TTI.getArithmeticInstrCost(
@@ -6908,6 +6905,28 @@ static bool planContainsAdditionalSimplifications(VPlan &Plan,
     return nullptr;
   };
 
+  // Check if a select for a safe divisor was hoisted to the pre-header. If so,
+  // the select doesn't need to be considered for the vector loop cost; go with
+  // the more accurate VPlan-based cost model.
+  for (VPRecipeBase &R : *Plan.getVectorPreheader()) {
+    auto *VPI = dyn_cast<VPInstruction>(&R);
+    if (!VPI || VPI->getOpcode() != Instruction::Select ||
+        VPI->getNumUsers() != 1)
+      continue;
+
+    if (auto *WR = dyn_cast<VPWidenRecipe>(*VPI->user_begin())) {
+      switch (WR->getOpcode()) {
+      case Instruction::UDiv:
+      case Instruction::SDiv:
+      case Instruction::URem:
+      case Instruction::SRem:
+        return true;
+      default:
+        break;
+      }
+    }
+  }
+
   DenseSet<Instruction *> SeenInstrs;
   auto Iter = vp_depth_first_deep(Plan.getVectorLoopRegion()->getEntry());
   for (VPBasicBlock *VPBB : VPBlockUtils::blocksOnly<VPBasicBlock>(Iter)) {


Regression tests:
file: llvm/test/Transforms/LoopVectorize/AArch64/predicated-costs.ll
commands: opt -p loop-vectorize -S %s
tests: test_name: srem_sdiv_with_tail_folding
test_body: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux"

define void @srem_sdiv_with_tail_folding(i32 %d.0, i32 %d.1, ptr %dst, i32 %end) #0 {
entry:
  br label %loop.header

loop.header:                                      ; preds = %loop.latch, %entry
  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop.latch ]
  %iv.sub = add nsw i32 %iv, -1
  %rem = srem i32 %iv.sub, %d.0
  %rem.1 = add nsw i32 %rem, 1
  %c = icmp eq i32 %rem.1, %d.0
  br i1 %c, label %then, label %loop.latch

then:                                             ; preds = %loop.header
  %div = sdiv i32 %iv.sub, %d.1
  %add.1 = add i32 %div, 1
  %add.1.ext = sext i32 %add.1 to i64
  %gep.dst = getelementptr i32, ptr %dst, i64 %add.1.ext
  store i32 %iv, ptr %gep.dst, align 4
  br label %loop.latch

loop.latch:                                       ; preds = %then, %loop.header
  %iv.next = add nuw nsw i32 %iv, 1
  %ec = icmp ne i32 %iv.next, %end
  br i1 %ec, label %loop.header, label %exit

exit:                                             ; preds = %loop.latch
  ret void
}

attributes #0 = { "target-cpu"="neoverse-v1" }

test_name: srem_sdiv_without_tail_folding
test_body: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux"

define void @srem_sdiv_without_tail_folding(i32 %d.0, i32 %d.1, ptr %dst, i32 %end) #0 {
entry:
  br label %loop.header

loop.header:                                      ; preds = %loop.latch, %entry
  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop.latch ]
  %iv.sub = add nsw i32 %iv, -1
  %rem = srem i32 %iv.sub, %d.0
  %rem.1 = add nsw i32 %rem, 1
  %c = icmp eq i32 %rem.1, %d.0
  br i1 %c, label %then, label %loop.latch

then:                                             ; preds = %loop.header
  %div = sdiv i32 %iv.sub, %d.1
  %add.1 = add i32 %div, 1
  %add.1.ext = sext i32 %add.1 to i64
  %gep.dst = getelementptr i32, ptr %dst, i64 %add.1.ext
  store i32 %iv, ptr %gep.dst, align 4
  br label %loop.latch

loop.latch:                                       ; preds = %then, %loop.header
  %iv.next = add nuw nsw i32 %iv, 1
  %ec = icmp ne i32 %iv.next, %end
  br i1 %ec, label %loop.header, label %exit

exit:                                             ; preds = %loop.latch
  ret void
}

attributes #0 = { "target-cpu"="neoverse-v2" }

test_name: test_predicated_load_cast_hint
test_body: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux"

define void @test_predicated_load_cast_hint(ptr %dst.1, ptr %dst.2, ptr %src, i8 %n, i64 %off) #0 {
entry:
  %n.ext = sext i8 %n to i32
  %n.sub = add i32 %n.ext, -15
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i8 [ 0, %entry ], [ %iv.next, %loop ]
  %l = load i8, ptr %src, align 1
  %l.ext = zext i8 %l to i64
  %add = or i64 %l.ext, 1
  %iv.ext = zext i8 %iv to i64
  %gep.dst.1 = getelementptr [16 x i64], ptr %dst.1, i64 %iv.ext, i64 %off
  store i64 %add, ptr %gep.dst.1, align 8
  store i8 0, ptr %dst.2, align 1
  %iv.next = add i8 %iv, 4
  %iv.next.ext = zext i8 %iv.next to i32
  %cmp = icmp sgt i32 %n.sub, %iv.next.ext
  br i1 %cmp, label %loop, label %exit, !llvm.loop !0

exit:                                             ; preds = %loop
  ret void
}

attributes #0 = { "target-cpu"="neoverse-v1" }

!0 = distinct !{!0, !1, !2, !3}
!1 = !{!"llvm.loop.mustprogress"}
!2 = !{!"llvm.loop.vectorize.predicate.enable", i1 true}
!3 = !{!"llvm.loop.vectorize.enable", i1 true}