Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/LoopVectorize.cpp: LoopVectorizationPlanner::computeBestVF
Issue title: [LLVM][RISCV][ICE] LoopVectorize Assertion Failure in computeBestVF()
Labels: vectorizers
crash
Reported by: whileone-sk

llvm::LoopVectorizationPlanner::computeBestVF(): Assertion `(BestFactor.Width == LegacyVF.Width || BestPlan.hasEarlyExit() || !Legal->getLAI()->getSymbolicStrides().empty() || 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.

 
RUN script red-e0c444.sh to reproduce the issue.
[red-e0c444.sh](https://github.com/user-attachments/files/23827986/red-e0c444.sh)
[red-e0c444.c](https://github.com/user-attachments/files/23827993/red-e0c444.c)

Attached backtrace.
[verbose-log.txt](https://github.com/user-attachments/files/23828005/verbose-log.txt)

Reduced testcase.
[red.c](https://github.com/user-attachments/files/23828018/red.c)


Found via Fuzzer.


Fix patch:
commit 24b87b8d4891d90afd8c4033a4997dedecbdd107
Author: Florian Hahn <flo@fhahn.com>
Date:   Sat Nov 29 22:00:30 2025 +0000

    [VPlan] Skip cost verification for loops with EVL gather/scatter.
    
    The VPlan-based cost model use vp_gather/vp_scatter for gather/scatter
    costs, which is different to the legacy cost model and cannot be matched
    there. Don't verify the costs match for plans containing gather/scatters
    with EVL.
    
    Fixes https://github.com/llvm/llvm-project/issues/169948.

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index c8495f1f32bd..4a89f7dd8672 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -7187,17 +7187,29 @@ VectorizationFactor LoopVectorizationPlanner::computeBestVF() {
   VPCostContext CostCtx(CM.TTI, *CM.TLI, BestPlan, CM, CM.CostKind,
                         *CM.PSE.getSE(), OrigLoop);
   precomputeCosts(BestPlan, BestFactor.Width, CostCtx);
-  // Verify that the VPlan-based and legacy cost models agree, except for VPlans
-  // with early exits and plans with additional VPlan simplifications. The
-  // legacy cost model doesn't properly model costs for such loops.
-  assert((BestFactor.Width == LegacyVF.Width || BestPlan.hasEarlyExit() ||
-          !Legal->getLAI()->getSymbolicStrides().empty() ||
-          planContainsAdditionalSimplifications(getPlanFor(BestFactor.Width),
-                                                CostCtx, OrigLoop,
-                                                BestFactor.Width) ||
-          planContainsAdditionalSimplifications(
-              getPlanFor(LegacyVF.Width), CostCtx, OrigLoop, LegacyVF.Width)) &&
-         " VPlan cost model and legacy cost model disagreed");
+  // Verify that the VPlan-based and legacy cost models agree, except for
+  // * VPlans with early exits,
+  // * VPlans with additional VPlan simplifications,
+  // * EVL-based VPlans with gather/scatters (the VPlan-based cost model uses
+  //   vp_scatter/vp_gather).
+  // The legacy cost model doesn't properly model costs for such loops.
+  bool UsesEVLGatherScatter =
+      any_of(VPBlockUtils::blocksOnly<VPBasicBlock>(vp_depth_first_shallow(
+                 BestPlan.getVectorLoopRegion()->getEntry())),
+             [](VPBasicBlock *VPBB) {
+               return any_of(*VPBB, [](VPRecipeBase &R) {
+                 return isa<VPWidenLoadEVLRecipe, VPWidenStoreEVLRecipe>(&R) &&
+                        !cast<VPWidenMemoryRecipe>(&R)->isConsecutive();
+               });
+             });
+  assert(
+      (BestFactor.Width == LegacyVF.Width || BestPlan.hasEarlyExit() ||
+       !Legal->getLAI()->getSymbolicStrides().empty() || UsesEVLGatherScatter ||
+       planContainsAdditionalSimplifications(
+           getPlanFor(BestFactor.Width), CostCtx, OrigLoop, BestFactor.Width) ||
+       planContainsAdditionalSimplifications(
+           getPlanFor(LegacyVF.Width), CostCtx, OrigLoop, LegacyVF.Width)) &&
+      " VPlan cost model and legacy cost model disagreed");
   assert((BestFactor.Width.isScalar() || BestFactor.ScalarCost > 0) &&
          "when vectorizing, the scalar cost must be computed.");
 #endif


Regression tests:
file: llvm/test/Transforms/LoopVectorize/RISCV/gather-scatter-cost.ll
commands: opt < %s -passes=loop-vectorize -mtriple riscv64 -mattr=+rva23u64 -S
opt < %s -passes=loop-vectorize -mtriple riscv64 -mattr=+rva23u64,+zvl1024b -S
tests: test_name: mixed_gather_scatters
test_body: define i8 @mixed_gather_scatters(ptr %A, ptr %B, ptr %C) #0 {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
  %accum = phi i8 [ 0, %entry ], [ %or.4, %loop ]
  %ptr.0 = load ptr, ptr %A, align 8
  %val.0 = load i64, ptr %ptr.0, align 8
  %cmp.0 = icmp sgt i64 %val.0, 0
  %ext.0 = zext i1 %cmp.0 to i8
  %or.0 = or i8 %accum, %ext.0
  %ptr.1 = load ptr, ptr %B, align 8
  %val.1 = load i64, ptr %ptr.1, align 8
  %cmp.1 = icmp sgt i64 %val.1, 0
  %ext.1 = zext i1 %cmp.1 to i8
  %or.1 = or i8 %or.0, %ext.1
  %or.2 = or i8 %or.1, 1
  %ptr.4 = load ptr, ptr %C, align 8
  %val.4 = load i64, ptr %ptr.4, align 8
  %cmp.4 = icmp sgt i64 %val.4, 0
  %ext.4 = zext i1 %cmp.4 to i8
  %or.4 = or i8 %or.2, %ext.4
  %iv.next = add i32 %iv, 1
  %exitcond = icmp eq i32 %iv, 9
  br i1 %exitcond, label %exit, label %loop

exit:                                             ; preds = %loop
  ret i8 %or.4
}

attributes #0 = { "target-features"="+zve64x,+zvl256b" }