Bug type: crash
Component: LoopFuse
Bug location: llvm/lib/Transforms/Scalar/LoopFuse.cpp: fuseGuardedLoops
performFusion
Issue title: crashes with assertion failure when running opt -passes=loop-fusion
Labels: crash
llvm:transforms
Reported by: k-arrows

Reproducer:
https://godbolt.org/z/qzjrq4P3z

Backtrace:
```console
opt: /root/llvm-project/llvm/include/llvm/Support/GenericLoopInfo.h:135: bool llvm::LoopBase<N, M>::contains(const BlockT*) const [with BlockT = llvm::BasicBlock; LoopT = llvm::Loop]: Assertion `!isInvalid() && "Loop not in a valid state!"' 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 -passes=loop-fusion <source>
1.	Running pass "function(loop-fusion)" on module "<source>"
2.	Running pass "loop-fusion" on function "main"
 #0 0x0000000005954738 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x5954738)
 #1 0x00000000059515e4 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #2 0x000071f9da242520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #3 0x000071f9da2969fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #4 0x000071f9da242476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #5 0x000071f9da2287f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #6 0x000071f9da22871b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #7 0x000071f9da239e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #8 0x0000000004f4c6b3 (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4f4c6b3)
 #9 0x0000000004f6c4e9 (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4f6c4e9)
#10 0x0000000004f6becf llvm::ScalarEvolution::getLoopDisposition(llvm::SCEV const*, llvm::Loop const*) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4f6becf)
#11 0x0000000004fbe4a4 llvm::ScalarEvolution::verify() const (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4fbe4a4)
#12 0x0000000004673741 (anonymous namespace)::LoopFuser::performFusion((anonymous namespace)::FusionCandidate const&, (anonymous namespace)::FusionCandidate const&) LoopFuse.cpp:0:0
#13 0x00000000046789ae (anonymous namespace)::LoopFuser::fuseCandidates() LoopFuse.cpp:0:0
#14 0x000000000467a501 llvm::LoopFusePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x467a501)
#15 0x0000000002f8e9de 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+0x2f8e9de)
#16 0x000000000572e961 llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x572e961)
#17 0x0000000000ef661e 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+0xef661e)
#18 0x000000000572ceca llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x572ceca)
#19 0x0000000000977e0e 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+0x977e0e)
#20 0x000000000572c881 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x572c881)
#21 0x0000000000981f1a 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, bool) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x981f1a)
#22 0x000000000097619e optMain (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x97619e)
#23 0x000071f9da229d90 (/lib/x86_64-linux-gnu/libc.so.6+0x29d90)
#24 0x000071f9da229e40 __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e40)
#25 0x000000000096d4f5 _start (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x96d4f5)
Program terminated with signal: SIGSEGV
Compiler returned: 139
```

Comments: author: k-arrows
body: Comes from llvm/test/CodeGen/PowerPC/ctrloop-sums.ll
author: dtcxzyw
body: Bisected to https://github.com/llvm/llvm-project/commit/d6072986cd40a3c009d2963a2b33501e047c7dc4
cc @1997alireza @CongzheUalberta
author: CongzheUalberta
body: Can you take a look? @1997alireza 
author: 1997alireza
body: > Can you take a look? [@1997alireza](https://github.com/1997alireza)

Yes, working on it.
author: 1997alireza
body: > > Can you take a look? [@1997alireza](https://github.com/1997alireza)
> 
> Yes, working on it.

Found the solution. Will post a patch to fix it asap.

author: CongzheUalberta
body: Should be fixed with #166233 so closing the issue now.
If something related happens down the road we can reopen it.

Fix patch:
commit 025e431e7450cada2724b19eb59354a6c020fa4f
Author: Alireza Torabian <alireza.torabian@huawei.com>
Date:   Tue Nov 4 16:48:39 2025 -0500

    [LoopFusion] Forget loop and block dispositions after latch merge (#166233)
    
    Merging the latches of loops may affect the dispositions, so they should
    be forgotten after the merge. This patch fixed the crash in loop fusion
    [#164082](https://github.com/llvm/llvm-project/issues/164082).

diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 19eccb9e1702..9ffa602416b0 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -1796,14 +1796,16 @@ private:
     // mergeLatch may remove the only block in FC1.
     SE.forgetLoop(FC1.L);
     SE.forgetLoop(FC0.L);
-    // Forget block dispositions as well, so that there are no dangling
-    // pointers to erased/free'ed blocks.
-    SE.forgetBlockAndLoopDispositions();
 
     // Move instructions from FC0.Latch to FC1.Latch.
     // Note: mergeLatch requires an updated DT.
     mergeLatch(FC0, FC1);
 
+    // Forget block dispositions as well, so that there are no dangling
+    // pointers to erased/free'ed blocks. It should be done after mergeLatch()
+    // since merging the latches may affect the dispositions.
+    SE.forgetBlockAndLoopDispositions();
+
     // Merge the loops.
     SmallVector<BasicBlock *, 8> Blocks(FC1.L->blocks());
     for (BasicBlock *BB : Blocks) {
@@ -2092,14 +2094,16 @@ private:
     // mergeLatch may remove the only block in FC1.
     SE.forgetLoop(FC1.L);
     SE.forgetLoop(FC0.L);
-    // Forget block dispositions as well, so that there are no dangling
-    // pointers to erased/free'ed blocks.
-    SE.forgetBlockAndLoopDispositions();
 
     // Move instructions from FC0.Latch to FC1.Latch.
     // Note: mergeLatch requires an updated DT.
     mergeLatch(FC0, FC1);
 
+    // Forget block dispositions as well, so that there are no dangling
+    // pointers to erased/free'ed blocks. It should be done after mergeLatch()
+    // since merging the latches may affect the dispositions.
+    SE.forgetBlockAndLoopDispositions();
+
     // Merge the loops.
     SmallVector<BasicBlock *, 8> Blocks(FC1.L->blocks());
     for (BasicBlock *BB : Blocks) {


Regression tests:
file: llvm/test/Transforms/LoopFusion/pr164082.ll
commands: opt -passes=loop-fusion -disable-output -stats < %s 2>&1
tests: test_name: <module>
test_body: ; REQUIRES: asserts
; STAT: 1 loop-fusion - Loops fused

; C Code
;
;;  for (int i = 0; i < 100; ++i)
;;      Array[i][i] = -i;
;;  for (int row = 0; row < 100; ++row)
;;      for (int col = 0; col < 100; ++col)
;;          if (col != row)
;;              Array[row][col] = row + col;
;
; Loop fusion should not crash anymore as now forgetBlockAndLoopDispositions()
; is trigerred after mergeLatch() during the fusion.

define i32 @forget_dispositions() nounwind {
entry:
  %Array = alloca [100 x [100 x i32]], align 4
  br label %for.body

for.body:                                         ; preds = %for.body, %entry
  %indvars.iv33 = phi i64 [ 0, %entry ], [ %indvars.iv.next34, %for.body ]
  %0 = trunc i64 %indvars.iv33 to i32
  %sub = sub i32 0, %0
  %arrayidx2 = getelementptr inbounds [100 x [100 x i32]], ptr %Array, i64 0, i64 %indvars.iv33, i64 %indvars.iv33
  store i32 %sub, ptr %arrayidx2, align 4
  %indvars.iv.next34 = add i64 %indvars.iv33, 1
  %lftr.wideiv35 = trunc i64 %indvars.iv.next34 to i32
  %exitcond36 = icmp eq i32 %lftr.wideiv35, 100
  br i1 %exitcond36, label %for.cond6.preheader, label %for.body

for.cond6.preheader:                              ; preds = %for.body, %for.inc17
  %indvars.iv29 = phi i64 [ %indvars.iv.next30, %for.inc17 ], [ 0, %for.body ]
  br label %for.body8

for.body8:                                        ; preds = %for.inc14, %for.cond6.preheader
  %indvars.iv = phi i64 [ 0, %for.cond6.preheader ], [ %indvars.iv.next, %for.inc14 ]
  %1 = trunc i64 %indvars.iv to i32
  %2 = trunc i64 %indvars.iv29 to i32
  %cmp9 = icmp eq i32 %1, %2
  br i1 %cmp9, label %for.inc14, label %if.then

if.then:                                          ; preds = %for.body8
  %3 = add i64 %indvars.iv, %indvars.iv29
  %arrayidx13 = getelementptr inbounds [100 x [100 x i32]], ptr %Array, i64 0, i64 %indvars.iv29, i64 %indvars.iv
  %4 = trunc i64 %3 to i32
  store i32 %4, ptr %arrayidx13, align 4
  br label %for.inc14

for.inc14:                                        ; preds = %for.body8, %if.then
  %indvars.iv.next = add i64 %indvars.iv, 1
  %lftr.wideiv27 = trunc i64 %indvars.iv.next to i32
  %exitcond28 = icmp eq i32 %lftr.wideiv27, 100
  br i1 %exitcond28, label %for.inc17, label %for.body8

for.inc17:                                        ; preds = %for.inc14
  %indvars.iv.next30 = add i64 %indvars.iv29, 1
  %lftr.wideiv31 = trunc i64 %indvars.iv.next30 to i32
  %exitcond32 = icmp eq i32 %lftr.wideiv31, 100
  br i1 %exitcond32, label %for.exit, label %for.cond6.preheader

for.exit:                                    ; preds = %for.inc17
  ret i32 0
}