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

Testing using 3bd161e98d89d31696002994771b7761f1c74859

C testcase:
```c
char a;
unsigned b = 221;
short c = 35;
char d[6];
int main() {
  d[0] = 4079751220931503459;
  for (short e = 3; e; e += 3)
    for (short f = 0; f < 1; f += -1832)
      for (long g = ({
             __typeof__(0) h = !d[f * 1];
             h;
           });
           g < c - 3; g += b - 219)
        a -= 8;
}
```
`/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/clang -I/scratch/tc-testing/compiler-fuzz-ci/csmith-build/include -fsigned-char -fno-strict-aliasing -fwrapv -march=rv64gcv -flto -O3 -fuse-ld=lld red.c -o rv64gcv.out -w`

LLVM IR:
```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 i32 @main() #0 {
entry:
  br label %for.body14.preheader48

for.cond.cleanup:                                 ; preds = %for.body14.preheader48
  store i8 %1, ptr null, align 1
  ret i32 0

for.body14.preheader48:                           ; preds = %for.body14.preheader48, %entry
  %e.047 = phi i16 [ 0, %entry ], [ %add27, %for.body14.preheader48 ]
  %tobool6.not = icmp eq i8 0, 0
  %conv7 = zext i1 %tobool6.not to i64
  %0 = lshr i64 %conv7, 1
  %1 = trunc i64 %0 to i8
  %add27 = add i16 %e.047, 1
  %tobool.not = icmp eq i16 %add27, 0
  br i1 %tobool.not, label %for.cond.cleanup, label %for.body14.preheader48
}

attributes #0 = { "target-features"="+64bit,+a,+c,+d,+f,+m,+relax,+v,+zicsr,+zifencei,+zmmul,+zve32f,+zve32x,+zve64d,+zve64f,+zve64x,+zvl128b,+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,-zvl256b,-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:7389: llvm::VectorizationFactor llvm::LoopVectorizationPlanner::computeBestVF(): Assertion `(BestFactor.Width == LegacyVF.Width || planContainsAdditionalSimplifications(getPlanFor(BestFactor.Width), CostCtx, OrigLoop)) && " 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 "main"
 #0 0x000061eac73d9f50 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x3010f50)
 #1 0x000061eac73d735f llvm::sys::RunSignalHandlers() (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x300e35f)
 #2 0x000061eac73d74b5 SignalHandler(int) Signals.cpp:0:0
 #3 0x00007496e1242520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #4 0x00007496e12969fc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
 #5 0x00007496e12969fc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
 #6 0x00007496e12969fc pthread_kill ./nptl/pthread_kill.c:89:10
 #7 0x00007496e1242476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
 #8 0x00007496e12287f3 abort ./stdlib/abort.c:81:7
 #9 0x00007496e122871b _nl_load_domain ./intl/loadmsgcat.c:1177:9
#10 0x00007496e1239e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
#11 0x000061eac63ad810 llvm::LoopVectorizationPlanner::computeBestVF() (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x1fe4810)
#12 0x000061eac63c45bb llvm::LoopVectorizePass::processLoop(llvm::Loop*) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x1ffb5bb)
#13 0x000061eac63c75d1 llvm::LoopVectorizePass::runImpl(llvm::Function&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x1ffe5d1)
#14 0x000061eac63c7c47 llvm::LoopVectorizePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x1ffec47)
#15 0x000061eac5222626 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+0xe59626)
#16 0x000061eac71e5cef 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+0x2e1ccef)
#17 0x000061eac52257b6 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+0xe5c7b6)
#18 0x000061eac71e4a6b llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x2e1ba6b)
#19 0x000061eac5221a56 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+0xe58a56)
#20 0x000061eac71e2aad 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+0x2e19aad)
#21 0x000061eac4a4fde6 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+0x686de6)
#22 0x000061eac4a41b71 optMain (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x678b71)
#23 0x00007496e1229d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#24 0x00007496e1229e40 call_init ./csu/../csu/libc-start.c:128:20
#25 0x00007496e1229e40 __libc_start_main ./csu/../csu/libc-start.c:379:5
#26 0x000061eac4a37f25 _start (/scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt+0x66ef25)
zsh: IOT instruction (core dumped)  /scratch/tc-testing/tc-compiler-fuzz-trunk/build-gcv/build-llvm-linux/bin/opt
```
https://godbolt.org/z/x316voG8q

Found via fuzzer

Related/prior issue: https://github.com/llvm/llvm-project/issues/106780

cc @fhahn


Fix patch:
commit 3fe6a064f15cd854fd497594cc20e8b680cd2133
Author: Florian Hahn <flo@fhahn.com>
Date:   Wed Sep 4 20:50:05 2024 +0100

    [LV] Check if compare is truncated directly in getInstructionCost.
    
    The current check for truncated compares in getInstructionCost misses
    cases where either the first or both operands are constants.
    Check directly if the compare is marked for truncation. In that case,
    the minimum bitwidth is that of the operands.
    
    The patch also adds asserts to ensure that.
    
    This fixes a divergence between legacy and VPlan-based cost model, where
    the legacy cost model incorrectly estimated the cost of compares with
    truncated operands.
    
    Fixes https://github.com/llvm/llvm-project/issues/107171.

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 0200525a718d..0ccf442dac99 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -6606,9 +6606,20 @@ LoopVectorizationCostModel::getInstructionCost(Instruction *I,
   case Instruction::ICmp:
   case Instruction::FCmp: {
     Type *ValTy = I->getOperand(0)->getType();
+
     Instruction *Op0AsInstruction = dyn_cast<Instruction>(I->getOperand(0));
-    if (canTruncateToMinimalBitwidth(Op0AsInstruction, VF))
-      ValTy = IntegerType::get(ValTy->getContext(), MinBWs[Op0AsInstruction]);
+    (void)Op0AsInstruction;
+    assert((!canTruncateToMinimalBitwidth(Op0AsInstruction, VF) ||
+            canTruncateToMinimalBitwidth(I, VF)) &&
+           "truncating Op0 must imply truncating the compare");
+    if (canTruncateToMinimalBitwidth(I, VF)) {
+      assert(!canTruncateToMinimalBitwidth(Op0AsInstruction, VF) ||
+             MinBWs[I] == MinBWs[Op0AsInstruction] &&
+                 "if both the operand and the compare are marked for "
+                 "truncation, they must have the same bitwidth");
+      ValTy = IntegerType::get(ValTy->getContext(), MinBWs[I]);
+    }
+
     VectorTy = ToVectorTy(ValTy, VF);
     return TTI.getCmpSelInstrCost(I->getOpcode(), VectorTy, nullptr,
                                   cast<CmpInst>(I)->getPredicate(), CostKind,


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: 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
}

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: icmp_ops_narrowed_to_i1
test_body: target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "riscv64-unknown-linux-gnu"

define i8 @icmp_ops_narrowed_to_i1() #0 {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i16 [ 0, %entry ], [ %iv.next, %loop ]
  %c = icmp eq i8 0, 0
  %ext = zext i1 %c to i64
  %shr = lshr i64 %ext, 1
  %trunc = trunc i64 %shr to i8
  %iv.next = add i16 %iv, 1
  %ec = icmp eq i16 %iv.next, 100
  br i1 %ec, label %exit, label %loop

exit:                                             ; preds = %loop
  ret i8 %trunc
}

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

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
}