Bug type: crash
Component: LoopFuse
Bug location: llvm/lib/Transforms/Scalar/LoopFuse.cpp: performFusion
Issue title: [LoopFusion] Assert "Fused candidate should be eligible for fusion"
Labels: loopoptim
crash
loop-fusion
Reported by: zitongzhoueric

To reproduce: https://compiler-explorer.com/z/oqacnTqoa
```opt -S -passes=loop-fusion -debug-only=loop-fusion ```  crashes on the LLVMIR generated from ```clang -O1``` because of an assertion failure
Stack Dump:
```
opt: /root/llvm-project/llvm/lib/Transforms/Scalar/LoopFuse.cpp:1038: bool {anonymous}::LoopFuser::fuseCandidates(): Assertion `FusedCand.isEligibleForFusion(SE) && "Fused candidate should be eligible for fusion!"' 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 -S -passes=loop-fusion -debug-only=loop-fusion <source>
1.	Running pass "function(loop-fusion)" on module "<source>"
2.	Running pass "loop-fusion" on function "main"
 #0 0x000000000513f3c8 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x513f3c8)
 #1 0x000000000513cdbc SignalHandler(int) Signals.cpp:0:0
 #2 0x0000719331442520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
 #3 0x00007193314969fc pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x969fc)
 #4 0x0000719331442476 gsignal (/lib/x86_64-linux-gnu/libc.so.6+0x42476)
 #5 0x00007193314287f3 abort (/lib/x86_64-linux-gnu/libc.so.6+0x287f3)
 #6 0x000071933142871b (/lib/x86_64-linux-gnu/libc.so.6+0x2871b)
 #7 0x0000719331439e96 (/lib/x86_64-linux-gnu/libc.so.6+0x39e96)
 #8 0x0000000003d8fdf8 (anonymous namespace)::LoopFuser::fuseCandidates() LoopFuse.cpp:0:0
 #9 0x0000000003d90d61 llvm::LoopFusePass::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x3d90d61)
#10 0x0000000002f9003e 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+0x2f9003e)
#11 0x0000000004f3cde8 llvm::PassManager<llvm::Function, llvm::AnalysisManager<llvm::Function>>::run(llvm::Function&, llvm::AnalysisManager<llvm::Function>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4f3cde8)
#12 0x0000000000e3a56e 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+0xe3a56e)
#13 0x0000000004f3b80e llvm::ModuleToFunctionPassAdaptor::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4f3b80e)
#14 0x0000000000e39c7e 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+0xe39c7e)
#15 0x0000000004f3b240 llvm::PassManager<llvm::Module, llvm::AnalysisManager<llvm::Module>>::run(llvm::Module&, llvm::AnalysisManager<llvm::Module>&) (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x4f3b240)
#16 0x00000000009108ea 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+0x9108ea)
#17 0x000000000090324a optMain (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x90324a)
#18 0x0000719331429d90 (/lib/x86_64-linux-gnu/libc.so.6+0x29d90)
#19 0x0000719331429e40 __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e40)
#20 0x00000000008facce _start (/opt/compiler-explorer/clang-assertions-trunk/bin/opt+0x8facce)
Program terminated with signal: SIGSEGV
Compiler returned: 139
```
The IR
```
define dso_local noundef i32 @main() local_unnamed_addr #0 {
entry:
  %vla48 = alloca [1024 x i32], align 16
  %vla149 = alloca [1024 x i32], align 16
  br label %for.body

for.body:
  %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]
  %call = tail call i32 @rand() #2
  %rem = srem i32 %call, 100
  %add = add nsw i32 %rem, 1
  %arrayidx = getelementptr inbounds i32, ptr %vla48, i64 %indvars.iv
  store i32 %add, ptr %arrayidx, align 4
  %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1
  %exitcond.not = icmp eq i64 %indvars.iv.next, 1024
  br i1 %exitcond.not, label %for.body4, label %for.body

for.body4:
  %indvars.iv54 = phi i64 [ %indvars.iv.next55, %for.body4 ], [ 0, %for.body ]
  %arrayidx6 = getelementptr inbounds i32, ptr %vla48, i64 %indvars.iv54
  %0 = load i32, ptr %arrayidx6, align 4
  %mul = shl nsw i32 %0, 1
  %arrayidx8 = getelementptr inbounds i32, ptr %vla149, i64 %indvars.iv54
  store i32 %mul, ptr %arrayidx8, align 4
  %indvars.iv.next55 = add nuw nsw i64 %indvars.iv54, 1
  %exitcond57.not = icmp eq i64 %indvars.iv.next55, 1024
  br i1 %exitcond57.not, label %for.body14, label %for.body4

for.body14:
  %indvars.iv58 = phi i64 [ %indvars.iv.next59, %for.inc26 ], [ 0, %for.body4 ]
  %arrayidx16 = getelementptr inbounds i32, ptr %vla48, i64 %indvars.iv58
  %1 = load i32, ptr %arrayidx16, align 4
  %2 = and i32 %1, 1
  %cmp18 = icmp eq i32 %2, 0
  br i1 %cmp18, label %if.then, label %for.inc26

if.then:
  %mul23 = mul nsw i32 %1, %1
  %arrayidx25 = getelementptr inbounds i32, ptr %vla149, i64 %indvars.iv58
  store i32 %mul23, ptr %arrayidx25, align 4
  br label %for.inc26

for.inc26:
  %indvars.iv.next59 = add nuw nsw i64 %indvars.iv58, 1
  %exitcond61.not = icmp eq i64 %indvars.iv.next59, 1024
  br i1 %exitcond61.not, label %for.end28, label %for.body14

for.end28:
  %arrayidx30 = getelementptr inbounds i8, ptr %vla149, i64 4092
  %3 = load i32, ptr %arrayidx30, align 4
  ret i32 %3
}

declare i32 @rand() local_unnamed_addr #1

attributes #0 = { mustprogress norecurse nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { nounwind }
```

This is generated from the cpp program:
```cpp
#include <random>

int main() {
    int n = 1024;
    int array1[n];
    int array2[n];
    int i;
    for (i = 0; i < n; i++) {
        array1[i] = std::rand() % 100 + 1;
    }
    for (i = 0; i < n; i++) {
        array2[i] = array1[i] * 2;
    }
    for (i = 0; i < n; i++) {
        if (array1[i] % 2 == 0) {
            array2[i] = array1[i] * array1[i];
        } 
    }
    return array2[n-1];
}
```

Comments: author: kasuga-fj
body: I did a quick check and it looks like the SCEV cache is not invalidated properly. I added `SE.forgetLoop(FC0.L)` after `mergeLatch`, which resolved the problem. But I'm not sure if this is a proper fix, and I don't understand the comment `Note: Need to forget the loops before merging the loop latches, ...` in the code.
https://github.com/llvm/llvm-project/blob/cb4ae35de0b4c19149379f16c7b279d80a669f9d/llvm/lib/Transforms/Scalar/LoopFuse.cpp#L1725-L1737
author: madhur13490
body: Looking into this.
author: 1997alireza
body: The issue is fixed by PR #177455. Close it now.

Fix patch:
commit 599c2731b3bbe9add135558377dbee2e1fa24358
Author: Alireza Torabian <alireza.torabian@huawei.com>
Date:   Thu Jan 22 19:50:21 2026 -0500

    [LoopFusion] Forget cached SCEV values after the fusion (#177455)
    
    This patch fixes the issue #115279. After the fusion, some of the cached
    SCEV values such as the induction variable may not be valid anymore and
    need to be forgotten.

diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 9c86b73dfc8d..0deeb557ddab 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -1702,6 +1702,10 @@ private:
     // since merging the latches may affect the dispositions.
     SE.forgetBlockAndLoopDispositions();
 
+    // Forget the cached SCEV values including the induction variable that may
+    // have changed after the fusion.
+    SE.forgetLoop(FC0.L);
+
     // Merge the loops.
     SmallVector<BasicBlock *, 8> Blocks(FC1.L->blocks());
     for (BasicBlock *BB : Blocks) {


Regression tests:
file: llvm/test/Transforms/LoopFusion/pr115279.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 (i = 0; i < n; i++)
;;      array2[i] = array1[i] * 2;
;;  for (i = 0; i < n; i++)
;;      if (array1[i] % 2 == 0)
;;          array2[i] = array1[i] * array1[i];
; Loop fusion should not crash due to incorrect cached SCEV values as now
; forgetLoop() is used to reset them after the fusion.

define dso_local noundef i32 @forget_induction_scev(ptr noalias %array1, ptr noalias %array2) {
entry:
  br label %for.body

for.body:
  %indvars.iv54 = phi i64 [ %indvars.iv.next55, %for.body ], [ 0, %entry ]
  %arrayidx6 = getelementptr inbounds i32, ptr %array1, i64 %indvars.iv54
  %0 = load i32, ptr %arrayidx6, align 4
  %mul = shl nsw i32 %0, 1
  %arrayidx8 = getelementptr inbounds i32, ptr %array2, i64 %indvars.iv54
  store i32 %mul, ptr %arrayidx8, align 4
  %indvars.iv.next55 = add nuw nsw i64 %indvars.iv54, 1
  %exitcond57.not = icmp eq i64 %indvars.iv.next55, 1024
  br i1 %exitcond57.not, label %for.body14, label %for.body

for.body14:
  %indvars.iv58 = phi i64 [ %indvars.iv.next59, %for.inc26 ], [ 0, %for.body ]
  %arrayidx16 = getelementptr inbounds i32, ptr %array1, i64 %indvars.iv58
  %1 = load i32, ptr %arrayidx16, align 4
  %2 = and i32 %1, 1
  %cmp18 = icmp eq i32 %2, 0
  br i1 %cmp18, label %if.then, label %for.inc26

if.then:
  %mul23 = mul nsw i32 %1, %1
  %arrayidx25 = getelementptr inbounds i32, ptr %array2, i64 %indvars.iv58
  store i32 %mul23, ptr %arrayidx25, align 4
  br label %for.inc26

for.inc26:
  %indvars.iv.next59 = add nuw nsw i64 %indvars.iv58, 1
  %exitcond61.not = icmp eq i64 %indvars.iv.next59, 1024
  br i1 %exitcond61.not, label %for.end28, label %for.body14

for.end28:
  %arrayidx30 = getelementptr inbounds i8, ptr %array2, i64 4092
  %3 = load i32, ptr %arrayidx30, align 4
  ret i32 %3
}