Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/LoopVectorize.cpp: VPRecipeBuilder::tryToBlend
llvm/lib/Transforms/Vectorize/VPlan.h: VPBlendRecipe
clone
getIncomingValue
getMask
getNumIncomingValues
llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp: VPBlendRecipe::print
Issue title: [LoopVectorize][VPlan] Assertion "Trying to access a single scalar per part but has multiple scalars per part." failed.
Labels: vectorizers
crash
Reported by: patrick-rivos

Reduced LLVM IR
```llvm ir
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.body.i

for.body.i:                                       ; preds = %if.end.i, %entry
  %q.0139.i = phi i16 [ 0, %entry ], [ %add.i, %if.end.i ]
  %idxprom.i = sext i16 0 to i64
  br i1 false, label %if.end.i, label %if.then.i

if.then.i:                                        ; preds = %for.body.i
  br i1 false, label %cond.end41.i, label %cond.end41.i

cond.end41.i:                                     ; preds = %if.then.i, %if.then.i
  br label %if.end.i

if.end.i:                                         ; preds = %cond.end41.i, %for.body.i
  %.in.i = phi ptr [ null, %cond.end41.i ], [ poison, %for.body.i ]
  %0 = load i16, ptr %.in.i, align 2
  %add.i = add i16 %q.0139.i, 1
  %tobool.not.i = icmp eq i16 %add.i, 0
  br i1 %tobool.not.i, label %m.exit, label %for.body.i

m.exit:                                           ; preds = %if.end.i
  %.lcssa = phi i16 [ %0, %if.end.i ]
  ret i32 0
}

attributes #0 = { "target-features"="+64bit,+v" }
```

Backtrace:
```
opt: /scratch/tc-testing/tc-apr-2/llvm/llvm/lib/Transforms/Vectorize/VPlan.cpp:249: llvm::Value* llvm::VPTransformState::get(llvm::VPValue*, unsigned int, bool): Assertion `(VF.isScalar() || Def->isLiveIn() || (hasScalarValue(Def, VPIteration(Part, 0)) && Data.PerPartScalars[Def][Part].size() == 1)) && "Trying to access a single scalar per part but has multiple scalars " "per part."' 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-apr-2/build-rv64gcv/build-llvm-linux/bin/opt --passes=loop-vectorize reduced.ll
 #0 0x00005ed60f56eb60 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x2d84b60)
 #1 0x00005ed60f56bf6f llvm::sys::RunSignalHandlers() (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x2d81f6f)
 #2 0x00005ed60f56c0c5 SignalHandler(int) Signals.cpp:0:0
 #3 0x00007f973de42520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #4 0x00007f973de969fc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
 #5 0x00007f973de969fc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
 #6 0x00007f973de969fc pthread_kill ./nptl/pthread_kill.c:89:10
 #7 0x00007f973de42476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
 #8 0x00007f973de287f3 abort ./stdlib/abort.c:81:7
 #9 0x00007f973de2871b _nl_load_domain ./intl/loadmsgcat.c:1177:9
#10 0x00007f973de39e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
#11 0x00005ed60e6cbb72 llvm::VPTransformState::get(llvm::VPValue*, unsigned int, bool) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1ee1b72)
#12 0x00005ed60e6fa9a6 llvm::VPInstruction::generatePerPart(llvm::VPTransformState&, unsigned int) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1f109a6)
#13 0x00005ed60e6fbbdf llvm::VPInstruction::execute(llvm::VPTransformState&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1f11bdf)
#14 0x00005ed60e6d74ff llvm::VPBasicBlock::execute(llvm::VPTransformState*) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1eed4ff)
#15 0x00005ed60e6cd9e0 llvm::VPRegionBlock::execute(llvm::VPTransformState*) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1ee39e0)
#16 0x00005ed60e6d90e6 llvm::VPlan::execute(llvm::VPTransformState*) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1eef0e6)
#17 0x00005ed60e5d76f3 llvm::LoopVectorizationPlanner::executePlan(llvm::ElementCount, unsigned int, llvm::VPlan&, llvm::InnerLoopVectorizer&, llvm::DominatorTree*, bool, llvm::DenseMap<llvm::SCEV const*, llvm::Value*, llvm::DenseMapInfo<llvm::SCEV const*, void>, llvm::detail::DenseMapPair<llvm::SCEV const*, llvm::Value*>> const*) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1ded6f3)
#18 0x00005ed60e5e9718 llvm::LoopVectorizePass::processLoop(llvm::Loop*) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1dff718)
#19 0x00005ed60e5eb73e llvm::LoopVectorizePass::runImpl(llvm::Function&, llvm::ScalarEvolution&, llvm::LoopInfo&, llvm::TargetTransformInfo&, llvm::DominatorTree&, llvm::BlockFrequencyInfo*, llvm::TargetLibraryInfo*, llvm::DemandedBits&, llvm::AssumptionCache&, llvm::LoopAccessInfoManager&, llvm::OptimizationRemarkEmitter&, llvm::ProfileSummaryInfo*) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1e0173e)
#20 0x00005ed60e5ec88d llvm::LoopVectorizePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x1e0288d)
#21 0x00005ed60d4f3276 llvm::detail::PassModel<llvm::Function, llvm::LoopVectorizePass, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0xd09276)
#22 0x00005ed60f395141 llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x2bab141)
#23 0x00005ed60d4eabd6 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-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0xd00bd6)
#24 0x00005ed60f393e3b llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x2ba9e3b)
#25 0x00005ed60d4f2dc6 llvm::detail::PassModel<llvm::Module, llvm::ModuleToFunctionPassAdaptor, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0xd08dc6)
#26 0x00005ed60f391cb1 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x2ba7cb1)
#27 0x00005ed60cd6cce5 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-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x582ce5)
#28 0x00005ed60cd5f316 optMain (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x575316)
#29 0x00007f973de29d90 __libc_start_call_main ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#30 0x00007f973de29e40 call_init ./csu/../csu/libc-start.c:128:20
#31 0x00007f973de29e40 __libc_start_main ./csu/../csu/libc-start.c:379:5
#32 0x00005ed60cd54f95 _start (/scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt+0x56af95)
zsh: IOT instruction (core dumped)  /scratch/tc-testing/tc-apr-2/build-rv64gcv/build-llvm-linux/bin/opt
```
Godbolt: https://godbolt.org/z/jYWea8z4q

Found via fuzzer.


Fix patch:
commit c83698367125703827f1b739393f006c399cb213
Author: Florian Hahn <flo@fhahn.com>
Date:   Tue Apr 9 11:14:05 2024 +0100

    [VPlan] Remove unused first mask op from VPBlendRecipe. (#87770)
    
    VPBlendRecipe does not use the first mask operand. Removing it allows
    VPlan-based DCE to remove unused mask computations.
    
    This also fixes #87410, where unused Not VPInstructions are considered
    having only their first lane demanded, but some of their operands
    providing a vector value due to other users.
    
    Fixes https://github.com/llvm/llvm-project/issues/87410
    
    PR: https://github.com/llvm/llvm-project/pull/87770

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index fd54faf17ca3..5535cc55e932 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -8221,6 +8221,8 @@ VPBlendRecipe *VPRecipeBuilder::tryToBlend(PHINode *Phi,
   // builder. At this point we generate the predication tree. There may be
   // duplications since this is a simple recursive scan, but future
   // optimizations will clean it up.
+  // TODO: At the moment the first mask is always skipped, but it would be
+  // better to skip the most expensive mask.
   SmallVector<VPValue *, 2> OperandsWithMask;
 
   for (unsigned In = 0; In < NumIncoming; In++) {
@@ -8233,6 +8235,8 @@ VPBlendRecipe *VPRecipeBuilder::tryToBlend(PHINode *Phi,
              "Distinct incoming values with one having a full mask");
       break;
     }
+    if (In == 0)
+      continue;
     OperandsWithMask.push_back(EdgeMask);
   }
   return new VPBlendRecipe(Phi, OperandsWithMask);
diff --git a/llvm/lib/Transforms/Vectorize/VPlan.h b/llvm/lib/Transforms/Vectorize/VPlan.h
index 5f6334b974cf..5dc905a3c407 100644
--- a/llvm/lib/Transforms/Vectorize/VPlan.h
+++ b/llvm/lib/Transforms/Vectorize/VPlan.h
@@ -1932,14 +1932,12 @@ public:
 class VPBlendRecipe : public VPSingleDefRecipe {
 public:
   /// The blend operation is a User of the incoming values and of their
-  /// respective masks, ordered [I0, M0, I1, M1, ...]. Note that a single value
-  /// might be incoming with a full mask for which there is no VPValue.
+  /// respective masks, ordered [I0, I1, M1, I2, M2, ...]. Note that the first
+  /// incoming value does not have a mask associated.
   VPBlendRecipe(PHINode *Phi, ArrayRef<VPValue *> Operands)
       : VPSingleDefRecipe(VPDef::VPBlendSC, Operands, Phi, Phi->getDebugLoc()) {
-    assert(Operands.size() > 0 &&
-           ((Operands.size() == 1) || (Operands.size() % 2 == 0)) &&
-           "Expected either a single incoming value or a positive even number "
-           "of operands");
+    assert((Operands.size() + 1) % 2 == 0 &&
+           "Expected an odd number of operands");
   }
 
   VPRecipeBase *clone() override {
@@ -1949,15 +1947,20 @@ public:
 
   VP_CLASSOF_IMPL(VPDef::VPBlendSC)
 
-  /// Return the number of incoming values, taking into account that a single
+  /// Return the number of incoming values, taking into account that the first
   /// incoming value has no mask.
   unsigned getNumIncomingValues() const { return (getNumOperands() + 1) / 2; }
 
   /// Return incoming value number \p Idx.
-  VPValue *getIncomingValue(unsigned Idx) const { return getOperand(Idx * 2); }
+  VPValue *getIncomingValue(unsigned Idx) const {
+    return Idx == 0 ? getOperand(0) : getOperand(Idx * 2 - 1);
+  }
 
   /// Return mask number \p Idx.
-  VPValue *getMask(unsigned Idx) const { return getOperand(Idx * 2 + 1); }
+  VPValue *getMask(unsigned Idx) const {
+    assert(Idx > 0 && "First index has no mask associated.");
+    return getOperand(Idx * 2);
+  }
 
   /// Generate the phi/select nodes.
   void execute(VPTransformState &State) override;
diff --git a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
index 2438e4dae3eb..625319954e9b 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanRecipes.cpp
@@ -1513,6 +1513,8 @@ void VPBlendRecipe::print(raw_ostream &O, const Twine &Indent,
     for (unsigned I = 0, E = getNumIncomingValues(); I < E; ++I) {
       O << " ";
       getIncomingValue(I)->printAsOperand(O, SlotTracker);
+      if (I == 0)
+        continue;
       O << "/";
       getMask(I)->printAsOperand(O, SlotTracker);
     }


Regression tests:
file: llvm/test/Transforms/LoopVectorize/unused-blend-mask-for-first-operand.ll
commands: opt -p loop-vectorize -force-vector-width=4 -force-vector-interleave=1 -S %s
tests: test_name: test_not_first_lane_only_constant
test_body: target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"

define void @test_not_first_lane_only_constant(ptr %A, ptr noalias %B) {
entry:
  br label %loop.header

loop.header:                                      ; preds = %loop.latch, %entry
  %iv = phi i16 [ 0, %entry ], [ %iv.next, %loop.latch ]
  %gep.A = getelementptr inbounds i16, ptr %A, i16 %iv
  br i1 false, label %loop.latch, label %else.1

else.1:                                           ; preds = %loop.header
  br i1 false, label %then.2, label %else.2

then.2:                                           ; preds = %else.1
  br label %else.2

else.2:                                           ; preds = %then.2, %else.1
  br label %loop.latch

loop.latch:                                       ; preds = %else.2, %loop.header
  %merge = phi ptr [ %B, %else.2 ], [ poison, %loop.header ]
  %l = load i16, ptr %merge, align 2
  %iv.next = add i16 %iv, 1
  store i16 %l, ptr %gep.A, align 2
  %c.2 = icmp eq i16 %iv.next, 1000
  br i1 %c.2, label %exit, label %loop.header

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

test_name: test_not_first_lane_only_wide_compare
test_body: target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"

define void @test_not_first_lane_only_wide_compare(ptr %A, ptr noalias %B, i16 %x, i16 %y) {
entry:
  br label %loop.header

loop.header:                                      ; preds = %loop.latch, %entry
  %iv = phi i16 [ 0, %entry ], [ %iv.next, %loop.latch ]
  %gep.A = getelementptr inbounds i16, ptr %A, i16 %iv
  %l.0 = load i16, ptr %gep.A, align 2
  %c.0 = icmp ult i16 %l.0, %x
  br i1 %c.0, label %loop.latch, label %else.1

else.1:                                           ; preds = %loop.header
  %c.1 = icmp ult i16 %l.0, %y
  br i1 %c.1, label %then.2, label %else.2

then.2:                                           ; preds = %else.1
  br label %else.2

else.2:                                           ; preds = %then.2, %else.1
  br label %loop.latch

loop.latch:                                       ; preds = %else.2, %loop.header
  %merge = phi ptr [ %B, %else.2 ], [ poison, %loop.header ]
  %l = load i16, ptr %merge, align 2
  %iv.next = add i16 %iv, 1
  store i16 %l, ptr %gep.A, align 2
  %c.2 = icmp eq i16 %iv.next, 1000
  br i1 %c.2, label %exit, label %loop.header

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