Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/LoopVectorize.cpp: VPRecipeBuilder::collectScaledReductions
VPRecipeBuilder::getScaledReductions
VPRecipeBuilder::tryToCreatePartialReduction
VPRecipeBuilder::tryToCreateWidenRecipe
Issue title: [LV] operands with different vscale types
Labels: vectorizers
crash
Reported by: sjoerdmeijer

Compiling this reproducer:

```
short a;
extern short b[];
void c(bool d[][1][1]) {
#pragma clang loop vectorize_predicate(enable)
  for (short e = 0; e < 858; e += 3) {
    b[e] = d[0644][e][1];
    for (char f = 0; f < 21; f++)
      a += d[0644][e][f];
  }
}
```

with `-O2 -mcpu=grace` results in:

```
 #4 0x000000000518a9aa llvm::SelectionDAG::isConstantIntBuildVectorOrConstantInt(llvm::SDValue, bool) const (/opt/compiler-explorer/clang-trunk/bin/clang+++0x518a9aa)
 #5 0x000000000518ab8b llvm::SelectionDAG::canonicalizeCommutativeBinop(unsigned int, llvm::SDValue&, llvm::SDValue&) const (/opt/compiler-explorer/clang-trunk/bin/clang+++0x518ab8b)
 #6 0x00000000051ad64d llvm::SelectionDAG::getNode(unsigned int, llvm::SDLoc const&, llvm::EVT, llvm::SDValue, llvm::SDValue, llvm::SDNodeFlags) (/opt/compiler-explorer/clang-trunk/bin/clang+++0x51ad64d)
 #7 0x000000000533de2e llvm::DAGTypeLegalizer::PromoteIntRes_SimpleIntBinOp(llvm::SDNode*) (/opt/compiler-explorer/clang-trunk/bin/clang+++0x533de2e)
 #8 0x000000000535821b llvm::DAGTypeLegalizer::PromoteIntegerResult(llvm::SDNode*, unsigned int) (/opt/compiler-explorer/clang-trunk/bin/clang+++0x535821b)
 #9 0x00000000052771da llvm::DAGTypeLegalizer::run() (/opt/compiler-explorer/clang-trunk/bin/clang+++0x52771da)
#10 0x0000000005277821 llvm::SelectionDAG::LegalizeTypes() 
```

See also: https://godbolt.org/z/vz87Koh4q

I've tried creating an IR reproducer, but ran into some troubles that might be hint what is going on: `%20` is a`8 x i16`, which is used in an add in `%26` and mixed with a `4 x i16` operand:

```
 ...
  %20 = zext nneg <vscale x 8 x i8> %19 to <vscale x 8 x i16>
  %21 = getelementptr inbounds nuw i16, ptr @b, <vscale x 8 x i64> %15
  tail call void @llvm.masked.scatter.nxv8i16.nxv8p0(<vscale x 8 x i16> %20, <vscale x 8 x ptr> %21, i32 2, <vscale x 8 x i1> %14)
  %22 = tail call <vscale x 8 x i8> @llvm.masked.gather.nxv8i8.nxv8p0(<vscale x 8 x ptr> %17, i32 1, <vscale x 8 x i1> %14, <vscale x 8 x i8> poison)
  %23 = zext nneg <vscale x 8 x i8> %22 to <vscale x 8 x i16>
  %24 = select <vscale x 8 x i1> %14, <vscale x 8 x i16> %23, <vscale x 8 x i16> zeroinitializer
  %25 = tail call <vscale x 4 x i16> @llvm.vector.partial.reduce.add.nxv4i16.nxv8i16(<vscale x 4 x i16> %16, <vscale x 8 x i16> %24)
  %26 = add <vscale x 4 x i16> %25, <vscale x 8 x i16> %20
...
```



Comments: author: sjoerdmeijer
body: CC: @paulwalker-arm , @david-arm , @sdesmalen-arm , @fhahn 
author: topperc
body: An assertions build fails earlier in IRBuilder https://godbolt.org/z/zadvTnne8
author: sdesmalen-arm
body: @SamTebbs33 it seems the result from the partial reduction here is a `<vscale x 4 x i16>`, whereas the use (an `add`) of this value assumes a `<vscale x 8 x i16>`. Can you investigate?
author: fhahn
body: IR reproducer: https://llvm.godbolt.org/z/Mz5xe8vK1
author: fhahn
body: @sjoerdmeijer you might want to use `-Xclang -llvm-verify-each` if you are not building with assertions. This makes it a bit easier to pin-point which pass creates invalid IR.

Fix patch:
commit 3ad5765e2341b53bff480b7992b1ed428dc603d6
Author: Florian Hahn <flo@fhahn.com>
Date:   Thu Nov 6 21:45:57 2025 +0000

    [LV] Check all users of partial reductions in chain have same scale. (#162822)
    
    Check that all partial reductions in a chain are only used by other
    partial reductions with the same scale factor. Otherwise we end up
    creating users of scaled reductions where the types of the other
    operands don't match.
    
    A similar issue was addressed in
    https://github.com/llvm/llvm-project/pull/158603, but misses the chained
    cases.
    
    Fixes https://github.com/llvm/llvm-project/issues/162530.
    
    PR: https://github.com/llvm/llvm-project/pull/162822

diff --git a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
index 906fa2f857c2..b7224a33f47b 100644
--- a/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
+++ b/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp
@@ -7933,6 +7933,26 @@ void VPRecipeBuilder::collectScaledReductions(VFRange &Range) {
         (!Chain.ExtendB || ExtendIsOnlyUsedByPartialReductions(Chain.ExtendB)))
       ScaledReductionMap.try_emplace(Chain.Reduction, Pair.second);
   }
+
+  // Check that all partial reductions in a chain are only used by other
+  // partial reductions with the same scale factor. Otherwise we end up creating
+  // users of scaled reductions where the types of the other operands don't
+  // match.
+  for (const auto &[Chain, Scale] : PartialReductionChains) {
+    auto AllUsersPartialRdx = [ScaleVal = Scale, this](const User *U) {
+      auto *UI = cast<Instruction>(U);
+      if (isa<PHINode>(UI) && UI->getParent() == OrigLoop->getHeader()) {
+        return all_of(UI->users(), [ScaleVal, this](const User *U) {
+          auto *UI = cast<Instruction>(U);
+          return ScaledReductionMap.lookup_or(UI, 0) == ScaleVal;
+        });
+      }
+      return ScaledReductionMap.lookup_or(UI, 0) == ScaleVal ||
+             !OrigLoop->contains(UI->getParent());
+    };
+    if (!all_of(Chain.Reduction->users(), AllUsersPartialRdx))
+      ScaledReductionMap.erase(Chain.Reduction);
+  }
 }
 
 bool VPRecipeBuilder::getScaledReductions(
@@ -8116,11 +8136,8 @@ VPRecipeBase *VPRecipeBuilder::tryToCreateWidenRecipe(VPSingleDefRecipe *R,
   if (isa<LoadInst>(Instr) || isa<StoreInst>(Instr))
     return tryToWidenMemory(Instr, Operands, Range);
 
-  if (std::optional<unsigned> ScaleFactor = getScalingForReduction(Instr)) {
-    if (auto PartialRed =
-            tryToCreatePartialReduction(Instr, Operands, ScaleFactor.value()))
-      return PartialRed;
-  }
+  if (std::optional<unsigned> ScaleFactor = getScalingForReduction(Instr))
+    return tryToCreatePartialReduction(Instr, Operands, ScaleFactor.value());
 
   if (!shouldWiden(Instr, Range))
     return nullptr;
@@ -8154,9 +8171,9 @@ VPRecipeBuilder::tryToCreatePartialReduction(Instruction *Reduction,
       isa<VPPartialReductionRecipe>(BinOpRecipe))
     std::swap(BinOp, Accumulator);
 
-  if (ScaleFactor !=
-      vputils::getVFScaleFactor(Accumulator->getDefiningRecipe()))
-    return nullptr;
+  assert(ScaleFactor ==
+             vputils::getVFScaleFactor(Accumulator->getDefiningRecipe()) &&
+         "all accumulators in chain must have same scale factor");
 
   unsigned ReductionOpcode = Reduction->getOpcode();
   if (ReductionOpcode == Instruction::Sub) {


Regression tests:
file: llvm/test/Transforms/LoopVectorize/AArch64/partial-reduce-incomplete-chains.ll
commands: opt --mattr=+neon,+dotprod -passes=loop-vectorize -force-vector-interleave=1 -enable-epilogue-vectorization=false -S %s
tests: test_name: test_incomplete_chain_without_mul
test_body: target triple = "arm64-apple-macosx"

define i16 @test_incomplete_chain_without_mul(ptr noalias %dst, ptr %A, ptr %B) #0 {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %red = phi i16 [ 0, %entry ], [ %red.next, %loop ]
  %l.a = load i8, ptr %A, align 1
  %a.ext = zext i8 %l.a to i16
  store i16 %a.ext, ptr %dst, align 2
  %l.b = load i8, ptr %B, align 1
  %b.ext = zext i8 %l.b to i16
  %add = add i16 %red, %b.ext
  %add.1 = add i16 %add, %a.ext
  %red.next = add i16 %add.1, %b.ext
  %iv.next = add i64 %iv, 1
  %ec = icmp ult i64 %iv, 1024
  br i1 %ec, label %loop, label %exit

exit:                                             ; preds = %loop
  ret i16 %red.next
}

attributes #0 = { "target-cpu"="grace" }