Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/LoopVectorize.cpp: LoopVectorizationPlanner::computeBestVF
LoopVectorizationPlanner::precomputeCosts
planContainsAdditionalSimplifications
Issue title: [VPlan] Assertion " VPlan cost model and legacy cost model disagreed"' failed.
Labels: vectorizers
crash
Reported by: patrick-rivos

Tested using c4906588ce47de33d59bcd95f3e82ce2c3e61c23

Testcase:
```c
_Bool a;
char b;
unsigned char c;
void d(_Bool e[][6]) {
  for (signed char f = 0; f < c; f += 3)
    for (_Bool g = ({
           __typeof__(0) h = e[1][2];
           e[f][f] > h ?: h;
         });
         g; g = b)
      for (_Bool i = 0; i < (0 >= 0); i = 1)
        a = 0;
}
```
https://godbolt.org/z/Eab8s5e1K

Reduced LLVM IR Testcase:
```llvm ir
; ModuleID = 'reduced.bc'
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define void @d() #0 {
entry:
  br label %for.body

for.cond.cleanup.loopexit:                        ; preds = %for.cond.cleanup14
  ret void

for.body:                                         ; preds = %for.cond.cleanup14, %entry
  %f.039 = phi i8 [ %add, %for.cond.cleanup14 ], [ 0, %entry ]
  %0 = or i8 0, 0
  %extract.t = trunc i8 %0 to i1
  br i1 %extract.t, label %for.body22.lr.ph, label %for.cond.cleanup14

for.body22.lr.ph:                                 ; preds = %for.body
  store i8 0, ptr null, align 1
  br label %for.cond.cleanup14

for.cond.cleanup14:                               ; preds = %for.body22.lr.ph, %for.body
  %add = add i8 %f.039, 1
  %conv = sext i8 %f.039 to i32
  %cmp = icmp slt i32 %conv, 1
  br i1 %cmp, label %for.body, label %for.cond.cleanup.loopexit
}

attributes #0 = { "target-features"="+64bit,+a,+c,+d,+f,+m,+relax,+v,+zicsr,+zifencei,+zmmul,+zve32f,+zve32x,+zve64d,+zve64f,+zve64x,+zvl128b,+zvl256b,+zvl32b,+zvl64b,-b,-e,-experimental-smctr,-experimental-smmpm,-experimental-smnpm,-experimental-ssctr,-experimental-ssnpm,-experimental-sspm,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-experimental-zvbc32e,-experimental-zvkgs,-h,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssqosid,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl16384b,-zvl2048b,-zvl32768b,-zvl4096b,-zvl512b,-zvl65536b,-zvl8192b" }
```

Command/backtrace:
```
> /scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt -passes=loop-vectorize reduced.ll -S
opt: /scratch/tc-testing/tc-compiler-fuzz-trunk/llvm/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp:7365: llvm::VectorizationFactor llvm::LoopVectorizationPlanner::computeBestVF(): Assertion `(BestFactor.Width == LegacyVF.Width || planContainsAdditionalSimplifications(getPlanFor(BestFactor.Width), BestFactor.Width, CostCtx, OrigLoop, CM)) && " 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.
Stack dump:
0.      Program arguments: /scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt -passes=loop-vectorize reduced.ll -S
1.      Running pass "function(loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>)" on module "reduced.ll"
2.      Running pass "loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>" on function "d"
 #0 0x000064cd5b412560 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x301e560)
 #1 0x000064cd5b40f96f llvm::sys::RunSignalHandlers() (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x301b96f)
 #2 0x000064cd5b40fac5 SignalHandler(int) Signals.cpp:0:0
 #3 0x000072ec5a042520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #4 0x000072ec5a0969fc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
 #5 0x000072ec5a0969fc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
 #6 0x000072ec5a0969fc pthread_kill ./nptl/pthread_kill.c:89:10
 #7 0x000072ec5a042476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
 #8 0x000072ec5a0287f3 abort ./stdlib/abort.c:81:7
 #9 0x000072ec5a02871b _nl_load_domain ./intl/loadmsgcat.c:1177:9
#10 0x000072ec5a039e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
#11 0x000064cd5a3dd109 llvm::LoopVectorizationPlanner::computeBestVF() (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x1fe9109)
#12 0x000064cd5a3f3ccb llvm::LoopVectorizePass::processLoop(llvm::Loop*) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x1fffccb)
#13 0x000064cd5a3f6ce1 llvm::LoopVectorizePass::runImpl(llvm::Function&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x2002ce1)
#14 0x000064cd5a3f7357 llvm::LoopVectorizePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x2003357)
#15 0x000064cd592541b6 llvm::detail::PassModel<llvm::Function, llvm::LoopVectorizePass, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0xe601b6)
#16 0x000064cd5b21d41f llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x2e2941f)
#17 0x000064cd59257346 llvm::detail::PassModel<llvm::Function, llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0xe63346)
#18 0x000064cd5b21c19b llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x2e2819b)
#19 0x000064cd592535e6 llvm::detail::PassModel<llvm::Module, llvm::ModuleToFunctionPassAdaptor, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0xe5f5e6)
#20 0x000064cd5b21a1dd llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x2e261dd)
#21 0x000064cd58a7c106 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) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x688106)
#22 0x000064cd58a6de91 optMain (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x679e91)
#23 0x000072ec5a029d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#24 0x000072ec5a029e40 call_init ./csu/../csu/libc-start.c:128:20
#25 0x000072ec5a029e40 __libc_start_main ./csu/../csu/libc-start.c:379:5
#26 0x000064cd58a64245 _start (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x670245)
zsh: IOT instruction (core dumped)  /scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt
```
https://godbolt.org/z/EzdEYKW7c

Found via fuzzer

Related/prior issue: #106417

cc @fhahn


Fix patch:
commit f0e34f381866b82a26241f7e9aa5964f0dd11ebd
Author: Florian Hahn <flo@fhahn.com>
Date:   Fri Aug 30 11:29:29 2024 +0100

    [VPlan] Don't skip optimizable truncs in planContainsAdditionalSimps.
    
    A optimizable cast can also be removed by VPlan simplifications. Remove
    the restriction from planContainsAdditionalSimplifications, as this
    causes it to miss relevant simplifications, triggering false positives
    for the cost decision verification.
    
    Also adds debug output for printing additional cost-precomputations.
    
    Fixes https://github.com/llvm/llvm-project/issues/106641.

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 6babfd1eee91..fa05b8dd2242 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -7147,7 +7147,12 @@ LoopVectorizationPlanner::precomputeCosts(VPlan &Plan, ElementCount VF,
     if (!OrigLoop->contains(CondI) ||
         !CostCtx.SkipCostComputation.insert(CondI).second)
       continue;
-    Cost += CostCtx.getLegacyCost(CondI, VF);
+    InstructionCost CondICost = CostCtx.getLegacyCost(CondI, VF);
+    LLVM_DEBUG({
+      dbgs() << "Cost of " << CondICost << " for VF " << VF
+             << ": exit condition instruction " << *CondI << "\n";
+    });
+    Cost += CondICost;
     for (Value *Op : CondI->operands()) {
       auto *OpI = dyn_cast<Instruction>(Op);
       if (!OpI || any_of(OpI->users(), [&ExitInstrs, this](User *U) {
@@ -7250,10 +7255,9 @@ InstructionCost LoopVectorizationPlanner::cost(VPlan &Plan,
 /// not have corresponding recipes in \p Plan and are not marked to be ignored
 /// in \p CostCtx. This means the VPlan contains simplification that the legacy
 /// cost-model did not account for.
-static bool
-planContainsAdditionalSimplifications(VPlan &Plan, ElementCount VF,
-                                      VPCostContext &CostCtx, Loop *TheLoop,
-                                      LoopVectorizationCostModel &CM) {
+static bool planContainsAdditionalSimplifications(VPlan &Plan,
+                                                  VPCostContext &CostCtx,
+                                                  Loop *TheLoop) {
   // First collect all instructions for the recipes in Plan.
   auto GetInstructionForCost = [](const VPRecipeBase *R) -> Instruction * {
     if (auto *S = dyn_cast<VPSingleDefRecipe>(R))
@@ -7284,16 +7288,13 @@ planContainsAdditionalSimplifications(VPlan &Plan, ElementCount VF,
   // Return true if the loop contains any instructions that are not also part of
   // the VPlan or are skipped for VPlan-based cost computations. This indicates
   // that the VPlan contains extra simplifications.
-  return any_of(
-      TheLoop->blocks(), [&SeenInstrs, VF, &CostCtx, &CM](BasicBlock *BB) {
-        return any_of(*BB, [&SeenInstrs, VF, &CostCtx, &CM](Instruction &I) {
-          if (isa<PHINode>(&I))
-            return false;
-          return !SeenInstrs.contains(&I) &&
-                 !CostCtx.skipCostComputation(&I, true) &&
-                 !CM.canTruncateToMinimalBitwidth(&I, VF);
-        });
-      });
+  return any_of(TheLoop->blocks(), [&SeenInstrs, &CostCtx](BasicBlock *BB) {
+    return any_of(*BB, [&SeenInstrs, &CostCtx](Instruction &I) {
+      if (isa<PHINode>(&I))
+        return false;
+      return !SeenInstrs.contains(&I) && !CostCtx.skipCostComputation(&I, true);
+    });
+  });
 }
 #endif
 
@@ -7364,8 +7365,7 @@ VectorizationFactor LoopVectorizationPlanner::computeBestVF() {
   precomputeCosts(BestPlan, BestFactor.Width, CostCtx);
   assert((BestFactor.Width == LegacyVF.Width ||
           planContainsAdditionalSimplifications(getPlanFor(BestFactor.Width),
-                                                BestFactor.Width, CostCtx,
-                                                OrigLoop, CM)) &&
+                                                CostCtx, OrigLoop)) &&
          " VPlan cost model and legacy cost model disagreed");
   assert((BestFactor.Width.isScalar() || BestFactor.ScalarCost > 0) &&
          "when vectorizing, the scalar cost must be computed.");


Regression tests:
file: llvm/test/Transforms/LoopVectorize/RISCV/truncate-to-minimal-bitwidth-cost.ll
commands: opt -p loop-vectorize -mattr=+v -S %s
tests: test_name: truncate_to_i1_used_by_branch
test_body: target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define void @truncate_to_i1_used_by_branch(i8 %x, ptr %dst) #0 {
entry:
  br label %loop.header

loop.header:                                      ; preds = %loop.latch, %entry
  %f.039 = phi i8 [ 0, %entry ], [ %add, %loop.latch ]
  %0 = or i8 23, %x
  %extract.t = trunc i8 %0 to i1
  br i1 %extract.t, label %then, label %loop.latch

then:                                             ; preds = %loop.header
  store i8 0, ptr %dst, align 1
  br label %loop.latch

loop.latch:                                       ; preds = %then, %loop.header
  %add = add i8 %f.039, 1
  %conv = sext i8 %f.039 to i32
  %cmp = icmp slt i32 %conv, 1
  br i1 %cmp, label %loop.header, label %exit

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

attributes #0 = { "target-features"="+64bit,+v,+zvl256b" }

test_name: test_pr98413_sext_removed
test_body: target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define void @test_pr98413_sext_removed(ptr %src, ptr noalias %dst, i64 %x) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %gep.src = getelementptr inbounds i16, ptr %src, i64 %iv
  %l = load i16, ptr %gep.src, align 8
  %ext.l = sext i16 %l to i64
  %and = and i64 %x, %ext.l
  %trunc.and = trunc i64 %and to i8
  %gep.dst = getelementptr inbounds i8, ptr %dst, i64 %iv
  store i8 %trunc.and, ptr %gep.dst, align 1
  %iv.next = add i64 %iv, 1
  %exitcond.not = icmp eq i64 %iv, 96
  br i1 %exitcond.not, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}

test_name: test_pr98413_zext_removed
test_body: target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define void @test_pr98413_zext_removed(ptr %src, ptr noalias %dst, i64 %x) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %gep.src = getelementptr inbounds i16, ptr %src, i64 %iv
  %l = load i16, ptr %gep.src, align 8
  %ext.l = zext i16 %l to i64
  %and = and i64 %x, %ext.l
  %trunc.and = trunc i64 %and to i8
  %gep.dst = getelementptr inbounds i8, ptr %dst, i64 %iv
  store i8 %trunc.and, ptr %gep.dst, align 1
  %iv.next = add i64 %iv, 1
  %exitcond.not = icmp eq i64 %iv, 96
  br i1 %exitcond.not, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}