Bug type: crash
Component: LoopVectorize
Bug location: llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp: VPlanTransforms::adjustFixedOrderRecurrences
sinkRecurrenceUsersAfterPrevious
Issue title: Assertion `hasPlanWithVF(ScalarVF) && "More than a single plan/VF w/o any plan having scalar VF"' failed.
Labels: vectorizers
crash-on-valid
Reported by: TatyanaDoubts

To reproduce run the following test with -passes=loop-vectorize
```
; ModuleID = './reduced.ll'
source_filename = "./reduced.ll"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128-ni:1-p2:32:8:8:32-ni:2"
target triple = "x86_64-unknown-linux-gnu"

define void @wombat() gc "statepoint-example" {
bb:
  br label %bb1

bb1:                                              ; preds = %bb1, %bb
  %phi = phi i64 [ %add, %bb1 ], [ 1, %bb ]
  %phi2 = phi i32 [ %trunc, %bb1 ], [ 1, %bb ]
  %phi3 = phi i32 [ %or, %bb1 ], [ 0, %bb ]
  %or = or i32 %phi2, 0
  %add = add i64 %phi, 1
  store i32 %phi3, ptr addrspace(1) null, align 4
  %icmp = icmp ult i64 %phi, 337
  %trunc = trunc i64 %phi to i32
  br i1 %icmp, label %bb1, label %bb4

bb4:                                              ; preds = %bb1
  ret void
}
```

Reproducer: https://godbolt.org/z/e14eWvxMb

Stack dump:
```
opt: /root/llvm-project/llvm/lib/Transforms/Vectorize/LoopVectorize.cpp:7304: llvm::VectorizationFactor llvm::LoopVectorizationPlanner::computeBestVF(): Assertion `hasPlanWithVF(ScalarVF) && "More than a single plan/VF w/o any plan having scalar VF"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
Stack dump:
0.	Program arguments: /opt/compiler-explorer/clang-assertions-trunk/bin/opt -o /app/output.s -S -passes=loop-vectorize <source>
1.	Running pass "function(loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>)" on module "<source>"
2.	Running pass "loop-vectorize<no-interleave-forced-only;no-vectorize-forced-only;>" on function "wombat"
 #0 0x0000000004fa4138 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4fa4138)
 #1 0x0000000004fa18ac SignalHandler(int) Signals.cpp:0:0
 #2 0x000073e073a42520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #3 0x000073e073a969fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #4 0x000073e073a42476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #5 0x000073e073a287f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #6 0x000073e073a2871b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #7 0x000073e073a39e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #8 0x0000000003fe5276 llvm::LoopVectorizationPlanner::computeBestVF() (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x3fe5276)
 #9 0x0000000004001214 llvm::LoopVectorizePass::processLoop(llvm::Loop*) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4001214)
#10 0x0000000004003eb9 llvm::LoopVectorizePass::runImpl(llvm::Function&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4003eb9)
#11 0x0000000004004543 llvm::LoopVectorizePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4004543)
#12 0x0000000002ec85ae llvm::detail::PassModel<llvm::Function, llvm::LoopVectorizePass, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x2ec85ae)
#13 0x0000000004da08f8 llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4da08f8)
#14 0x0000000000df11be llvm::detail::PassModel<llvm::Function, llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0xdf11be)
#15 0x0000000004d9f2fe llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4d9f2fe)
#16 0x0000000000df0aae llvm::detail::PassModel<llvm::Module, llvm::ModuleToFunctionPassAdaptor, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0xdf0aae)
#17 0x0000000004d9d490 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4d9d490)
#18 0x00000000008e8002 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) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x8e8002)
#19 0x00000000008daedc optMain (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x8daedc)
#20 0x000073e073a29d90 (/lib/x86_64-linux-gnu/libc.so.6+0x29d90)
#21 0x000073e073a29e40 __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e40)
#22 0x00000000008d296e _start (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x8d296e)
Program terminated with signal: SIGSEGV
Compiler returned: 139

Comments: author: fhahn
body: Very interesting issue thanks! Proposed fix https://github.com/llvm/llvm-project/issues/106523

Fix patch:
commit 2dfb1c664c0a0afc512b900c45989e247406e523
Author: Florian Hahn <flo@fhahn.com>
Date:   Wed Oct 23 13:12:03 2024 -0700

    [VPlan] Try to hoist Previous (and operands), if sinking fails for FORs. (#108945)
    
    In some cases, Previous (and its operands) can be hoisted. This allows
    supporting additional cases where sinking of all users of to FOR fails,
    e.g. due having to sink recipes with side-effects.
    
    This fixes a crash where we fail to create a scalar VPlan for a
    first-order recurrence, but can create a vector VPlan, because the trunc
    instruction of an IV which generates the previous value of the
    recurrence has been optimized to a truncated induction recipe, thus
    hoisting it to the beginning.
    
    Fixes https://github.com/llvm/llvm-project/issues/106523.
    
    PR: https://github.com/llvm/llvm-project/pull/108945

diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
index d50f3c0c3f3e..c6e09c4f2e6e 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.cpp
@@ -772,6 +772,105 @@ sinkRecurrenceUsersAfterPrevious(VPFirstOrderRecurrencePHIRecipe *FOR,
   return true;
 }
 
+/// Try to hoist \p Previous and its operands before all users of \p FOR.
+static bool hoistPreviousBeforeFORUsers(VPFirstOrderRecurrencePHIRecipe *FOR,
+                                        VPRecipeBase *Previous,
+                                        VPDominatorTree &VPDT) {
+  if (Previous->mayHaveSideEffects() || Previous->mayReadFromMemory())
+    return false;
+
+  // Collect recipes that need hoisting.
+  SmallVector<VPRecipeBase *> HoistCandidates;
+  SmallPtrSet<VPRecipeBase *, 8> Visited;
+  VPRecipeBase *HoistPoint = nullptr;
+  // Find the closest hoist point by looking at all users of FOR and selecting
+  // the recipe dominating all other users.
+  for (VPUser *U : FOR->users()) {
+    auto *R = dyn_cast<VPRecipeBase>(U);
+    if (!R)
+      continue;
+    if (!HoistPoint || VPDT.properlyDominates(R, HoistPoint))
+      HoistPoint = R;
+  }
+  assert(all_of(FOR->users(),
+                [&VPDT, HoistPoint](VPUser *U) {
+                  auto *R = dyn_cast<VPRecipeBase>(U);
+                  return !R || HoistPoint == R ||
+                         VPDT.properlyDominates(HoistPoint, R);
+                }) &&
+         "HoistPoint must dominate all users of FOR");
+
+  auto NeedsHoisting = [HoistPoint, &VPDT,
+                        &Visited](VPValue *HoistCandidateV) -> VPRecipeBase * {
+    VPRecipeBase *HoistCandidate = HoistCandidateV->getDefiningRecipe();
+    if (!HoistCandidate)
+      return nullptr;
+    VPRegionBlock *EnclosingLoopRegion =
+        HoistCandidate->getParent()->getEnclosingLoopRegion();
+    assert((!HoistCandidate->getParent()->getParent() ||
+            HoistCandidate->getParent()->getParent() == EnclosingLoopRegion) &&
+           "CFG in VPlan should still be flat, without replicate regions");
+    // Hoist candidate was already visited, no need to hoist.
+    if (!Visited.insert(HoistCandidate).second)
+      return nullptr;
+
+    // Candidate is outside loop region or a header phi, dominates FOR users w/o
+    // hoisting.
+    if (!EnclosingLoopRegion || isa<VPHeaderPHIRecipe>(HoistCandidate))
+      return nullptr;
+
+    // If we reached a recipe that dominates HoistPoint, we don't need to
+    // hoist the recipe.
+    if (VPDT.properlyDominates(HoistCandidate, HoistPoint))
+      return nullptr;
+    return HoistCandidate;
+  };
+  auto CanHoist = [&](VPRecipeBase *HoistCandidate) {
+    // Avoid hoisting candidates with side-effects, as we do not yet analyze
+    // associated dependencies.
+    return !HoistCandidate->mayHaveSideEffects();
+  };
+
+  if (!NeedsHoisting(Previous->getVPSingleValue()))
+    return true;
+
+  // Recursively try to hoist Previous and its operands before all users of FOR.
+  HoistCandidates.push_back(Previous);
+
+  for (unsigned I = 0; I != HoistCandidates.size(); ++I) {
+    VPRecipeBase *Current = HoistCandidates[I];
+    assert(Current->getNumDefinedValues() == 1 &&
+           "only recipes with a single defined value expected");
+    if (!CanHoist(Current))
+      return false;
+
+    for (VPValue *Op : Current->operands()) {
+      // If we reach FOR, it means the original Previous depends on some other
+      // recurrence that in turn depends on FOR. If that is the case, we would
+      // also need to hoist recipes involving the other FOR, which may break
+      // dependencies.
+      if (Op == FOR)
+        return false;
+
+      if (auto *R = NeedsHoisting(Op))
+        HoistCandidates.push_back(R);
+    }
+  }
+
+  // Order recipes to hoist by dominance so earlier instructions are processed
+  // first.
+  sort(HoistCandidates, [&VPDT](const VPRecipeBase *A, const VPRecipeBase *B) {
+    return VPDT.properlyDominates(A, B);
+  });
+
+  for (VPRecipeBase *HoistCandidate : HoistCandidates) {
+    HoistCandidate->moveBefore(*HoistPoint->getParent(),
+                               HoistPoint->getIterator());
+  }
+
+  return true;
+}
+
 bool VPlanTransforms::adjustFixedOrderRecurrences(VPlan &Plan,
                                                   VPBuilder &LoopBuilder) {
   VPDominatorTree VPDT;
@@ -795,7 +894,8 @@ bool VPlanTransforms::adjustFixedOrderRecurrences(VPlan &Plan,
       Previous = PrevPhi->getBackedgeValue()->getDefiningRecipe();
     }
 
-    if (!sinkRecurrenceUsersAfterPrevious(FOR, Previous, VPDT))
+    if (!sinkRecurrenceUsersAfterPrevious(FOR, Previous, VPDT) &&
+        !hoistPreviousBeforeFORUsers(FOR, Previous, VPDT))
       return false;
 
     // Introduce a recipe to combine the incoming and previous values of a
diff --git a/llvm/lib/Transforms/Vectorize/VPlanTransforms.h b/llvm/lib/Transforms/Vectorize/VPlanTransforms.h
index 60a44bfb0dca..11e094db6294 100644
--- a/llvm/lib/Transforms/Vectorize/VPlanTransforms.h
+++ b/llvm/lib/Transforms/Vectorize/VPlanTransforms.h
@@ -36,11 +36,11 @@ struct VPlanTransforms {
                                 GetIntOrFpInductionDescriptor,
                             ScalarEvolution &SE, const TargetLibraryInfo &TLI);
 
-  /// Sink users of fixed-order recurrences after the recipe defining their
-  /// previous value. Then introduce FirstOrderRecurrenceSplice VPInstructions
-  /// to combine the value from the recurrence phis and previous values. The
-  /// current implementation assumes all users can be sunk after the previous
-  /// value, which is enforced by earlier legality checks.
+  /// Try to have all users of fixed-order recurrences appear after the recipe
+  /// defining their previous value, by either sinking users or hoisting recipes
+  /// defining their previous value (and its operands). Then introduce
+  /// FirstOrderRecurrenceSplice VPInstructions to combine the value from the
+  /// recurrence phis and previous values.
   /// \returns true if all users of fixed-order recurrences could be re-arranged
   /// as needed or false if it is not possible. In the latter case, \p Plan is
   /// not valid.


Regression tests:
file: llvm/test/Transforms/LoopVectorize/X86/fixed-order-recurrence.ll
commands: opt -passes=loop-vectorize -S -o - %s
tests: test_name: test_pr62954_scalar_epilogue_required
test_body: target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc_linux"

define i64 @test_pr62954_scalar_epilogue_required(ptr %A, ptr noalias %B, ptr %C) {
entry:
  %gep = getelementptr i8, ptr %A, i64 872
  %rec.start = load i64, ptr %gep, align 8
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 1, %entry ], [ %iv.next, %loop ]
  %for = phi i64 [ %rec.start, %entry ], [ %neg.iv, %loop ]
  %gep.B = getelementptr double, ptr %B, i64 %iv
  %l.B = load double, ptr %gep.B, align 8
  %neg.iv = sub nsw i64 0, %iv
  store i64 %neg.iv, ptr %gep, align 8
  %iv.next = add nuw nsw i64 %iv, 2
  %ec = icmp ugt i64 %iv, 74
  br i1 %ec, label %exit, label %loop

exit:                                             ; preds = %loop
  %.in.lcssa = phi i64 [ %for, %loop ]
  %.lcssa = phi double [ %l.B, %loop ]
  store double %.lcssa, ptr %C, align 8
  ret i64 %.in.lcssa
}

test_name: thirdorderrec
test_body: target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc_linux"

define void @thirdorderrec(ptr nocapture noundef readonly %x, ptr noalias nocapture noundef writeonly %y, i32 noundef %n) {
entry:
  %cmp38 = icmp sgt i32 %n, 3
  br i1 %cmp38, label %for.body.preheader, label %for.cond.cleanup

for.body.preheader:                               ; preds = %entry
  %wide.trip.count = zext i32 %n to i64
  %.pre = load i8, ptr %x, align 1
  %arrayidx5.phi.trans.insert = getelementptr inbounds i8, ptr %x, i64 1
  %.pre44 = load i8, ptr %arrayidx5.phi.trans.insert, align 1
  %arrayidx12.phi.trans.insert = getelementptr inbounds i8, ptr %x, i64 2
  %.pre45 = load i8, ptr %arrayidx12.phi.trans.insert, align 1
  br label %for.body

for.cond.cleanup:                                 ; preds = %for.body, %entry
  ret void

for.body:                                         ; preds = %for.body, %for.body.preheader
  %0 = phi i8 [ %.pre45, %for.body.preheader ], [ %3, %for.body ]
  %1 = phi i8 [ %.pre44, %for.body.preheader ], [ %0, %for.body ]
  %2 = phi i8 [ %.pre, %for.body.preheader ], [ %1, %for.body ]
  %indvars.iv = phi i64 [ 3, %for.body.preheader ], [ %indvars.iv.next, %for.body ]
  %add8 = add i8 %1, %2
  %add15 = add i8 %add8, %0
  %arrayidx18 = getelementptr inbounds i8, ptr %x, i64 %indvars.iv
  %3 = load i8, ptr %arrayidx18, align 1
  %add21 = add i8 %add15, %3
  %arrayidx24 = getelementptr inbounds i8, ptr %y, i64 %indvars.iv
  store i8 %add21, ptr %arrayidx24, align 1
  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
  %exitcond.not = icmp eq i64 %indvars.iv.next, %wide.trip.count
  br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
}

test_name: for_iv_trunc_optimized
test_body: target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc_linux"

define void @for_iv_trunc_optimized(ptr %dst) {
bb:
  br label %loop

loop:                                             ; preds = %loop, %bb
  %iv = phi i64 [ 1, %bb ], [ %add, %loop ]
  %for.1 = phi i32 [ 1, %bb ], [ %trunc, %loop ]
  %for.2 = phi i32 [ 0, %bb ], [ %or, %loop ]
  %or = or i32 %for.1, 3
  %add = add i64 %iv, 1
  store i32 %for.2, ptr %dst, align 4
  %icmp = icmp ult i64 %iv, 337
  %trunc = trunc i64 %iv to i32
  br i1 %icmp, label %loop, label %exit

exit:                                             ; preds = %loop
  ret void
}

test_name: firstorderrec
test_body: target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc_linux"

define void @firstorderrec(ptr nocapture noundef readonly %x, ptr noalias nocapture noundef writeonly %y, i32 noundef %n) {
entry:
  %cmp18 = icmp sgt i32 %n, 1
  br i1 %cmp18, label %for.body.preheader, label %for.cond.cleanup

for.body.preheader:                               ; preds = %entry
  %wide.trip.count = zext i32 %n to i64
  %.pre = load i8, ptr %x, align 1
  br label %for.body

for.cond.cleanup:                                 ; preds = %for.body, %entry
  ret void

for.body:                                         ; preds = %for.body, %for.body.preheader
  %0 = phi i8 [ %.pre, %for.body.preheader ], [ %1, %for.body ]
  %indvars.iv = phi i64 [ 1, %for.body.preheader ], [ %indvars.iv.next, %for.body ]
  %arrayidx4 = getelementptr inbounds i8, ptr %x, i64 %indvars.iv
  %1 = load i8, ptr %arrayidx4, align 1
  %add7 = add i8 %1, %0
  %arrayidx10 = getelementptr inbounds i8, ptr %y, i64 %indvars.iv
  store i8 %add7, ptr %arrayidx10, align 1
  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
  %exitcond.not = icmp eq i64 %indvars.iv.next, %wide.trip.count
  br i1 %exitcond.not, label %for.cond.cleanup, label %for.body
}

file: llvm/test/Transforms/LoopVectorize/first-order-recurrence-chains-vplan.ll
commands: opt -passes=loop-vectorize -force-vector-width=4 -force-vector-interleave=1 -debug-only=loop-vectorize -disable-output -S %s 2>&1
tests: test_name: test_chained_first_order_recurrences_4
test_body: define i32 @test_chained_first_order_recurrences_4(ptr %base, i64 %x) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ %iv.next, %loop ], [ 0, %entry ]
  %for.x = phi i64 [ %for.x.next, %loop ], [ 0, %entry ]
  %for.y = phi i32 [ %for.x.prev, %loop ], [ 0, %entry ]
  %iv.next = add i64 %iv, 1
  %gep = getelementptr i64, ptr %base, i64 %iv
  %for.x.prev = trunc i64 %for.x to i32
  %for.y.i64 = sext i32 %for.y to i64
  store i64 %for.y.i64, ptr %gep, align 4
  %for.x.next = mul i64 %x, 2
  %icmp = icmp ugt i64 %iv, 4096
  br i1 %icmp, label %ret, label %loop

ret:                                              ; preds = %loop
  ret i32 0
}

test_name: test_chained_first_order_recurrences_3
test_body: define void @test_chained_first_order_recurrences_3(ptr %ptr) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %for.1 = phi i16 [ 22, %entry ], [ %for.1.next, %loop ]
  %for.2 = phi i16 [ 33, %entry ], [ %for.1, %loop ]
  %for.3 = phi i16 [ 33, %entry ], [ %for.2, %loop ]
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %iv.next = add nuw nsw i64 %iv, 1
  %gep.ptr = getelementptr inbounds i16, ptr %ptr, i64 %iv
  %for.1.next = load i16, ptr %gep.ptr, align 2
  %add.1 = add i16 %for.1, %for.2
  %add.2 = add i16 %add.1, %for.3
  store i16 %add.2, ptr %gep.ptr, align 2
  %exitcond.not = icmp eq i64 %iv.next, 1000
  br i1 %exitcond.not, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}

test_name: test_chained_first_order_recurrences_1
test_body: define void @test_chained_first_order_recurrences_1(ptr %ptr) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %for.1 = phi i16 [ 22, %entry ], [ %for.1.next, %loop ]
  %for.2 = phi i16 [ 33, %entry ], [ %for.1, %loop ]
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %iv.next = add nuw nsw i64 %iv, 1
  %gep.ptr = getelementptr inbounds i16, ptr %ptr, i64 %iv
  %for.1.next = load i16, ptr %gep.ptr, align 2
  %add = add i16 %for.1, %for.2
  store i16 %add, ptr %gep.ptr, align 2
  %exitcond.not = icmp eq i64 %iv.next, 1000
  br i1 %exitcond.not, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}

test_name: test_chained_first_order_recurrences_5_hoist_to_load
test_body: define i32 @test_chained_first_order_recurrences_5_hoist_to_load(ptr %base) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ %iv.next, %loop ], [ 0, %entry ]
  %for.x = phi i64 [ %for.x.next, %loop ], [ 0, %entry ]
  %for.y = phi i32 [ %for.x.prev, %loop ], [ 0, %entry ]
  %iv.next = add i64 %iv, 1
  %gep = getelementptr i64, ptr %base, i64 %iv
  %l = load i64, ptr %gep, align 4
  %for.x.prev = trunc i64 %for.x to i32
  %for.y.i64 = sext i32 %for.y to i64
  store i64 %for.y.i64, ptr %gep, align 4
  %for.x.next = mul i64 %l, 2
  %icmp = icmp ugt i64 %iv, 4096
  br i1 %icmp, label %ret, label %loop

ret:                                              ; preds = %loop
  ret i32 0
}

file: llvm/test/Transforms/LoopVectorize/first-order-recurrence-multiply-recurrences.ll
tests: test_name: test_pr54223_sink_after_insertion_order
test_body: define void @test_pr54223_sink_after_insertion_order(ptr noalias %a, ptr noalias %b, ptr noalias %dst) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %for.1 = phi float [ 0.000000e+00, %entry ], [ %for.1.next, %loop ]
  %for.2 = phi float [ 0.000000e+00, %entry ], [ %for.2.next, %loop ]
  %neg = fneg float %for.2
  %muladd = call float @llvm.fmuladd.f32(float %for.1, float %neg, float 0.000000e+00)
  %dst.gep = getelementptr inbounds float, ptr %dst, i64 %iv
  %iv.next = add nuw nsw i64 %iv, 1
  %for.1.next = load float, ptr %a, align 4
  %for.2.next = load float, ptr %b, align 4
  store float %muladd, ptr %dst.gep, align 4
  %exitcond.not = icmp eq i64 %iv.next, 10000
  br i1 %exitcond.not, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}

; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare float @llvm.fmuladd.f32(float, float, float) #0

attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }

test_name: test_pr54233_for_depend_on_each_other
test_body: define void @test_pr54233_for_depend_on_each_other(ptr noalias %a, ptr noalias %b) {
entry:
  br label %loop

loop:                                             ; preds = %loop, %entry
  %iv = phi i64 [ 0, %entry ], [ %iv.next, %loop ]
  %for.1 = phi i32 [ 0, %entry ], [ %for.1.next, %loop ]
  %for.2 = phi i32 [ 0, %entry ], [ %for.2.next, %loop ]
  %or = or i32 %for.2, 10
  %shl = shl i32 %for.2, %for.1
  %xor = xor i32 %shl, 255
  %and = and i32 %xor, %or
  %for.1.next = xor i32 12, %for.2
  %for.2.next = load i32, ptr %b, align 4
  %a.gep = getelementptr inbounds i32, ptr %a, i64 %iv
  store i32 %and, ptr %a.gep, align 4
  %iv.next = add nuw i64 %iv, 1
  %exitcond = icmp eq i64 %iv, 1000
  br i1 %exitcond, label %exit, label %loop

exit:                                             ; preds = %loop
  ret void
}