Bug type: crash
Component: LoopFuse
Bug location: llvm/lib/Transforms/Scalar/LoopFuse.cpp: canSinkInst
collectMovablePreheaderInsts
fixPHINodes
movePreheaderInsts
Issue title: [LoopFusion] Assert "Instruction does not dominate all uses"
Labels: loopoptim
crash-on-valid
loop-fusion
Reported by: sjoerdmeijer

This IR:

```
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-gnu"

define i8 @_Z3fn1ssb() {
entry:
  br label %for.body11.1

for.body11.1:                                     ; preds = %for.body11.1, %entry
  %indvars.iv.1 = phi i64 [ %indvars.iv.next.1, %for.body11.1 ], [ 2, %entry ]
  %indvars.iv.next.1 = add i64 %indvars.iv.1, 1
  %exitcond.1.not = icmp eq i64 %indvars.iv.next.1, 0
  br i1 %exitcond.1.not, label %for.body11.2.preheader, label %for.body11.1

for.body11.2.preheader:                           ; preds = %for.body11.1
  %.sroa.speculated.1.lcssa = phi i8 [ 0, %for.body11.1 ]
  br label %for.body11.2

for.body11.2:                                     ; preds = %for.body11.2, %for.body11.2.preheader
  %indvars.iv.2 = phi i64 [ %indvars.iv.next.2, %for.body11.2 ], [ 2, %for.body11.2.preheader ]
  %indvars.iv.next.2 = add i64 %indvars.iv.2, 1
  %exitcond.2.not = icmp eq i64 %indvars.iv.next.2, 0
  br i1 %exitcond.2.not, label %for.cond.cleanup6.2.loopexit, label %for.body11.2

for.cond.cleanup6.2.loopexit:                     ; preds = %for.body11.2
  %.sroa.speculated.2.lcssa = phi i8 [ %.sroa.speculated.1.lcssa, %for.body11.2 ]
  ret i8 %.sroa.speculated.2.lcssa
}
```

compiled with `opt -S -passes=loop-fusion `, triggers this assert:

```
Instruction does not dominate all uses!
  %.sroa.speculated.1.lcssa = phi i8 [ 0, %for.body11.1 ]
  %.sroa.speculated.2.lcssa = phi i8 [ %.sroa.speculated.1.lcssa, %for.body11.1 ]
opt: /root/llvm-project/llvm/lib/Transforms/Scalar/LoopFuse.cpp:1827: llvm::Loop* {anonymous}::LoopFuser::performFusion(const {anonymous}::FusionCandidate&, const {anonymous}::FusionCandidate&): Assertion `!verifyFunction(*FC0.Header->getParent(), &errs())' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
Stack dump:
0.	Program arguments: /opt/compiler-explorer/clang-assertions-trunk/bin/opt -o /app/output.s -S -S -passes=loop-fusion <source>
1.	Running pass "function(loop-fusion)" on module "<source>"
2.	Running pass "loop-fusion" on function "_Z3fn1ssb"
 #0 0x0000000005965e58 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x5965e58)
 #1 0x0000000005962d04 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #2 0x0000785d49242520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #3 0x0000785d492969fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #4 0x0000785d49242476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #5 0x0000785d492287f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #6 0x0000785d4922871b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #7 0x0000785d49239e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #8 0x000000000468b1fc (anonymous namespace)::LoopFuser::performFusion((anonymous namespace)::FusionCandidate const&, (anonymous namespace)::FusionCandidate const&) LoopFuse.cpp:0:0
 #9 0x000000000468fcfe (anonymous namespace)::LoopFuser::fuseCandidates() LoopFuse.cpp:0:0
#10 0x0000000004691861 llvm::LoopFusePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4691861)
#11 0x0000000002f977be llvm::detail::PassModel<llvm::Function, llvm::LoopFusePass, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x2f977be)

```

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

IR was reproduced from this C reproducer:

```
#include <algorithm>
unsigned char a;
extern int d[][1][1];
char b;
int c;
void fn1(short e, short f, bool g) {
  for (; b;)
    for (short h = 0; h < 011; h += 3)
      for (short i = 0; i < 2; i = 0201)
        for (int j = 2; j < c; j += 0 >= 0) {
          d[i][i][j] = std::max((long long)4, (long long)0);
          a = std::max(a, (unsigned char)(f && (e || g)));
        }
}
```

Comments: author: sjoerdmeijer
body: CC: @madhur13490 

Fix patch:
commit 1286de408cc4a3ba1bd6cb6fed7d9517c0429462
Author: Congzhe <congzhe.cao@huawei.com>
Date:   Thu Jan 22 16:12:12 2026 -0500

    [LoopFusion] Optimize away Phi nodes that are sunk from the 2nd loop preheader (#176503)
    
    Fixed issue #165087.
    
    When we sink phis from the 2nd loop preheader to the exit block, we
    optimize it a bit further, i.e., propagate the uses of each phi node with
    its incoming value and optimize away the phis. Deleted `fixPHINodes()`
    too because the phis are already optimized away and there is no point
    processing `fixPHINodes()`.

diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index d7284c98b48c..9c86b73dfc8d 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -1083,28 +1083,6 @@ private:
     return true;
   }
 
-  /// This function fixes PHI nodes after fusion in \p SafeToSink.
-  /// \p SafeToSink instructions are the instructions that are to be moved past
-  /// the fused loop. Thus, the PHI nodes in \p SafeToSink should be updated to
-  /// receive values from the fused loop if they are currently taking values
-  /// from the first loop (i.e. FC0)'s latch.
-  void fixPHINodes(ArrayRef<Instruction *> SafeToSink,
-                   const FusionCandidate &FC0,
-                   const FusionCandidate &FC1) const {
-    for (Instruction *Inst : SafeToSink) {
-      // No update needed for non-PHI nodes.
-      PHINode *Phi = dyn_cast<PHINode>(Inst);
-      if (!Phi)
-        continue;
-      for (unsigned I = 0; I < Phi->getNumIncomingValues(); I++) {
-        if (Phi->getIncomingBlock(I) != FC0.Latch)
-          continue;
-        assert(FC1.Latch && "FC1 latch is not set");
-        Phi->setIncomingBlock(I, FC1.Latch);
-      }
-    }
-  }
-
   /// Collect instructions in the \p FC1 Preheader that can be hoisted
   /// to the \p FC0 Preheader or sunk into the \p FC1 Body
   bool collectMovablePreheaderInsts(
@@ -1453,11 +1431,19 @@ private:
     // insert instructions in reverse order to maintain dominance relationship
     for (Instruction *I : reverse(SinkInsts)) {
       assert(I->getParent() == FC1.Preheader);
-      I->moveBefore(*FC1.ExitBlock, FC1.ExitBlock->getFirstInsertionPt());
+      if (isa<PHINode>(I)) {
+        // The Phis to be sunk should have only one incoming value, as is
+        // assured by the condition that the second loop is dominated by the
+        // first one which is enforced by isStrictlyAdjacent().
+        // Replace the phi uses with the corresponding incoming value to clean
+        // up the code.
+        assert(cast<PHINode>(I)->getNumIncomingValues() == 1 &&
+               "Expected the sunk PHI node to have 1 incoming value.");
+        I->replaceAllUsesWith(I->getOperand(0));
+        I->eraseFromParent();
+      } else
+        I->moveBefore(*FC1.ExitBlock, FC1.ExitBlock->getFirstInsertionPt());
     }
-    // PHI nodes in SinkInsts need to be updated to receive values from the
-    // fused loop.
-    fixPHINodes(SinkInsts, FC0, FC1);
   }
 
   /// Determine if two fusion candidates have identical guards


Regression tests:
file: llvm/test/Transforms/LoopFusion/lcssa.ll
commands: opt -S -passes=loop-fusion < %s 2>&1
tests: test_name: test1
test_body: define void @test1() {
entry:
  br label %vector.ph

vector.ph:                                        ; preds = %middle.block, %entry
  br label %middle.block

middle.block:                                     ; preds = %vector.ph
  br i1 true, label %for.cond17.preheader, label %vector.ph

for.cond17.preheader:                             ; preds = %middle.block
  %.lcssa92 = phi i16 [ 1, %middle.block ]
  br label %vector.ph61

vector.ph61:                                      ; preds = %vector.ph61, %for.cond17.preheader
  br i1 true, label %for.end41, label %vector.ph61

for.end41:                                        ; preds = %vector.ph61
  ret void
}

file: llvm/test/Transforms/LoopFusion/pr165087.ll
commands: opt -S -passes=loop-fusion < %s 2>&1
tests: test_name: <module>
test_body: 
; When the two loops are legal to get fused, and when there are phi nodes in the second loop preheader
; that will be sunk to the exit block, replace the phi uses with the corresponding incoming value.
; This fixes llvm issue 165087.

define i8 @_Z3fn1ssb() {
;
entry:
  br label %for.body11.1

for.body11.1:                                     ; preds = %for.body11.1, %entry
  %indvars.iv.1 = phi i64 [ %indvars.iv.next.1, %for.body11.1 ], [ 2, %entry ]
  %indvars.iv.next.1 = add i64 %indvars.iv.1, 1
  %exitcond.1.not = icmp eq i64 %indvars.iv.next.1, 0
  br i1 %exitcond.1.not, label %for.body11.2.preheader, label %for.body11.1

for.body11.2.preheader:                           ; preds = %for.body11.1
  %.sroa.speculated.1.lcssa = phi i8 [ 0, %for.body11.1 ]
  br label %for.body11.2

for.body11.2:                                     ; preds = %for.body11.2, %for.body11.2.preheader
  %indvars.iv.2 = phi i64 [ %indvars.iv.next.2, %for.body11.2 ], [ 2, %for.body11.2.preheader ]
  %indvars.iv.next.2 = add i64 %indvars.iv.2, 1
  %exitcond.2.not = icmp eq i64 %indvars.iv.next.2, 0
  br i1 %exitcond.2.not, label %for.cond.cleanup6.2.loopexit, label %for.body11.2

for.cond.cleanup6.2.loopexit:                     ; preds = %for.body11.2
  %.sroa.speculated.2.lcssa = phi i8 [ %.sroa.speculated.1.lcssa, %for.body11.2 ]
  ret i8 %.sroa.speculated.2.lcssa
}

file: llvm/test/Transforms/LoopFusion/sunk-phi-nodes.ll
commands: opt -passes=loop-fusion -S < %s 2>&1
tests: test_name: check_sunk_phi_nodes
test_body: define dso_local i32 @check_sunk_phi_nodes() {
entry:
  br label %for.body

for.body:                                         ; preds = %for.inc, %entry
  %sum1.02 = phi i32 [ 0, %entry ], [ %add, %for.inc ]
  %i.01 = phi i32 [ 0, %entry ], [ %inc, %for.inc ]
  %add = add nsw i32 %sum1.02, %i.01
  br label %for.inc

for.inc:                                          ; preds = %for.body
  %inc = add nsw i32 %i.01, 1
  %cmp = icmp slt i32 %inc, 10
  br i1 %cmp, label %for.body, label %for.end

for.end:                                          ; preds = %for.inc
  %sum1.0.lcssa = phi i32 [ %add, %for.inc ]
  br label %for.body4

for.body4:                                        ; preds = %for.inc6, %for.end
  %i1.04 = phi i32 [ 0, %for.end ], [ %inc7, %for.inc6 ]
  %sum2.03 = phi i32 [ 0, %for.end ], [ %add5, %for.inc6 ]
  %mul = mul nsw i32 %i1.04, %i1.04
  %add5 = add nsw i32 %sum2.03, %mul
  br label %for.inc6

for.inc6:                                         ; preds = %for.body4
  %inc7 = add nsw i32 %i1.04, 1
  %cmp3 = icmp slt i32 %inc7, 10
  br i1 %cmp3, label %for.body4, label %for.end8

for.end8:                                         ; preds = %for.inc6
  %sum2.0.lcssa = phi i32 [ %add5, %for.inc6 ]
  %0 = add i32 %sum1.0.lcssa, %sum2.0.lcssa
  ret i32 %0
}