Bug type: miscompilation
Component: LoopPeel
Bug location: llvm/lib/Transforms/Utils/LoopPeel.cpp: llvm::canPeelLastIteration
Issue title: [LoopFullUnroll] Miscompilation at O3
Labels: miscompilation
llvm:transforms
generated by fuzzer
Reported by: dtcxzyw

Reproducer:
```
; bin/opt -passes=loop-unroll-full reduced.ll -S
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"
target triple = "x86_64-unknown-linux-gnu"

@g_479 = global i8 -1
@crc32_tab = global [256 x i32] zeroinitializer
@.str.8 = constant [15 x i8] c"checksum = %X\0A\00"

define i32 @main() {
entry:
  br label %for.cond1.preheader.i

for.cond1.preheader.i:                            ; preds = %for.cond1.preheader.i, %entry
  %indvars.iv.i = phi i64 [ 0, %entry ], [ %indvars.iv.next.i, %for.cond1.preheader.i ]
  %0 = and i64 %indvars.iv.i, 20
  %tobool.not.4.not.i = icmp eq i64 %0, 16
  %xor.5.i = select i1 %tobool.not.4.not.i, i32 0, i32 1
  %arrayidx.i = getelementptr [256 x i32], ptr @crc32_tab, i64 0, i64 %indvars.iv.i
  store i32 %xor.5.i, ptr %arrayidx.i, align 4
  %indvars.iv.next.i = add i64 %indvars.iv.i, 1
  %exitcond.not.i = icmp eq i64 %indvars.iv.next.i, 256
  br i1 %exitcond.not.i, label %lbl_1516.us.us.i.i, label %for.cond1.preheader.i

lbl_1516.us.us.i.i:                               ; preds = %lbl_1587.us.us.i.i, %for.cond1.preheader.i
  %conv134690.us.us.i.i = phi i8 [ %spec.select.i.i, %lbl_1587.us.us.i.i ], [ 0, %for.cond1.preheader.i ]
  %l_1485.0.us.us.i.i = phi i16 [ %inc130.us.us.i.i, %lbl_1587.us.us.i.i ], [ -14340, %for.cond1.preheader.i ]
  %inc130.us.us.i.i = add i16 %l_1485.0.us.us.i.i, 1
  %tobool132.not.us.us.i.i = icmp eq i8 %conv134690.us.us.i.i, 0
  br i1 %tobool132.not.us.us.i.i, label %lbl_1587.us.us.i.i, label %land.rhs133.us.us.i.i

land.rhs133.us.us.i.i:                            ; preds = %lbl_1516.us.us.i.i
  store i8 25, ptr @g_479, align 1
  br label %lbl_1587.us.us.i.i

lbl_1587.us.us.i.i:                               ; preds = %land.rhs133.us.us.i.i, %lbl_1516.us.us.i.i
  %tobool293.not.not.us.us.i.i = icmp eq i16 %l_1485.0.us.us.i.i, 0
  %spec.select.i.i = select i1 %tobool293.not.not.us.us.i.i, i8 1, i8 0
  br i1 %tobool293.not.not.us.us.i.i, label %func_134.exit, label %lbl_1516.us.us.i.i

func_134.exit:                                    ; preds = %lbl_1587.us.us.i.i
  %1 = load i8, ptr @g_479, align 1
  %idxprom.i.i.i = zext i8 %1 to i64
  %arrayidx.i.i.i = getelementptr [256 x i32], ptr @crc32_tab, i64 0, i64 %idxprom.i.i.i
  %2 = load i32, ptr %arrayidx.i.i.i, align 4
  %call.i = call i32 (ptr, ...) @printf(ptr @.str.8, i32 %2)
  ret i32 0
}

declare i32 @printf(ptr, ...)
```
```
; 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"
target triple = "x86_64-unknown-linux-gnu"

@g_479 = global i8 -1
@crc32_tab = global [256 x i32] zeroinitializer
@.str.8 = constant [15 x i8] c"checksum = %X\0A\00"

define i32 @main() {
entry:
  br label %for.cond1.preheader.i

for.cond1.preheader.i:                            ; preds = %for.cond1.preheader.i, %entry
  %indvars.iv.i = phi i64 [ 0, %entry ], [ %indvars.iv.next.i, %for.cond1.preheader.i ]
  %0 = and i64 %indvars.iv.i, 20
  %tobool.not.4.not.i = icmp eq i64 %0, 16
  %xor.5.i = select i1 %tobool.not.4.not.i, i32 0, i32 1
  %arrayidx.i = getelementptr [256 x i32], ptr @crc32_tab, i64 0, i64 %indvars.iv.i
  store i32 %xor.5.i, ptr %arrayidx.i, align 4
  %indvars.iv.next.i = add i64 %indvars.iv.i, 1
  %exitcond.not.i = icmp eq i64 %indvars.iv.next.i, 256
  br i1 %exitcond.not.i, label %lbl_1516.us.us.i.i.preheader, label %for.cond1.preheader.i

lbl_1516.us.us.i.i.preheader:                     ; preds = %for.cond1.preheader.i
  br label %lbl_1516.us.us.i.i

lbl_1516.us.us.i.i:                               ; preds = %lbl_1516.us.us.i.i.preheader, %lbl_1587.us.us.i.i
  %conv134690.us.us.i.i = phi i8 [ %spec.select.i.i, %lbl_1587.us.us.i.i ], [ 0, %lbl_1516.us.us.i.i.preheader ]
  %l_1485.0.us.us.i.i = phi i16 [ %inc130.us.us.i.i, %lbl_1587.us.us.i.i ], [ -14340, %lbl_1516.us.us.i.i.preheader ]
  %inc130.us.us.i.i = add nsw i16 %l_1485.0.us.us.i.i, 1
  %tobool132.not.us.us.i.i = icmp eq i8 %conv134690.us.us.i.i, 0
  br i1 %tobool132.not.us.us.i.i, label %lbl_1587.us.us.i.i, label %land.rhs133.us.us.i.i

land.rhs133.us.us.i.i:                            ; preds = %lbl_1516.us.us.i.i
  store i8 25, ptr @g_479, align 1
  br label %lbl_1587.us.us.i.i

lbl_1587.us.us.i.i:                               ; preds = %land.rhs133.us.us.i.i, %lbl_1516.us.us.i.i
  %tobool293.not.not.us.us.i.i = icmp eq i16 %l_1485.0.us.us.i.i, -1
  %spec.select.i.i = select i1 %tobool293.not.not.us.us.i.i, i8 1, i8 0
  br i1 %tobool293.not.not.us.us.i.i, label %func_134.exit.peel.begin, label %lbl_1516.us.us.i.i, !llvm.loop !0

func_134.exit.peel.begin:                         ; preds = %lbl_1587.us.us.i.i
  %spec.select.i.i.lcssa = phi i8 [ %spec.select.i.i, %lbl_1587.us.us.i.i ]
  %inc130.us.us.i.i.lcssa = phi i16 [ %inc130.us.us.i.i, %lbl_1587.us.us.i.i ]
  br label %lbl_1516.us.us.i.i.peel

lbl_1516.us.us.i.i.peel:                          ; preds = %func_134.exit.peel.begin
  %inc130.us.us.i.i.peel = add i16 %inc130.us.us.i.i.lcssa, 1
  %tobool132.not.us.us.i.i.peel = icmp eq i8 %spec.select.i.i.lcssa, 0
  br i1 %tobool132.not.us.us.i.i.peel, label %lbl_1587.us.us.i.i.peel, label %land.rhs133.us.us.i.i.peel

land.rhs133.us.us.i.i.peel:                       ; preds = %lbl_1516.us.us.i.i.peel
  store i8 25, ptr @g_479, align 1
  br label %lbl_1587.us.us.i.i.peel

lbl_1587.us.us.i.i.peel:                          ; preds = %land.rhs133.us.us.i.i.peel, %lbl_1516.us.us.i.i.peel
  %tobool293.not.not.us.us.i.i.peel = icmp eq i16 %inc130.us.us.i.i.lcssa, 0
  %spec.select.i.i.peel = select i1 %tobool293.not.not.us.us.i.i.peel, i8 1, i8 0
  br i1 %tobool293.not.not.us.us.i.i.peel, label %func_134.exit.peel.next, label %func_134.exit.peel.next

func_134.exit.peel.next:                          ; preds = %lbl_1587.us.us.i.i.peel, %lbl_1587.us.us.i.i.peel
  br label %lbl_1516.us.us.i.i.peel.next

lbl_1516.us.us.i.i.peel.next:                     ; preds = %func_134.exit.peel.next
  br label %func_134.exit

func_134.exit:                                    ; preds = %lbl_1516.us.us.i.i.peel.next
  %1 = load i8, ptr @g_479, align 1
  %idxprom.i.i.i = zext i8 %1 to i64
  %arrayidx.i.i.i = getelementptr [256 x i32], ptr @crc32_tab, i64 0, i64 %idxprom.i.i.i
  %2 = load i32, ptr %arrayidx.i.i.i, align 4
  %call.i = call i32 (ptr, ...) @printf(ptr @.str.8, i32 %2)
  ret i32 0
}

declare i32 @printf(ptr, ...)

!0 = distinct !{!0, !1}
!1 = !{!"llvm.loop.peeled.count", i32 1}
```
Before:
```
checksum = 1
```
After:
```
checksum = 0
```
llvm version: 3ccb15d6caf57f2a866d496ada2fb52d14b179d2


Comments: author: dtcxzyw
body: Reverting https://github.com/llvm/llvm-project/commit/3fcfce4c5e5fd1c3072e8db1d692c93459e8cd74 fixes this miscompilation. And https://github.com/llvm/llvm-project/commit/7e14161f49b32387988cf9d937bbfaa27d0fbdd5 doesn't fix this problem. cc @fhahn 


author: fhahn
body: Issue is that we modify the exit condition w/o checking for other users.

Fix patch:
commit a0a2a1e095ddd092e668b0b3f9a7dfa9655255e1
Author: Florian Hahn <flo@fhahn.com>
Date:   Sun May 18 11:47:03 2025 +0100

    [LoopPeel] Make sure exit condition has a single use when peeling last.
    
    Update the check in canPeelLastIteration to make sure the exiting
    condition has a single use. When peeling the last iteration, we adjust
    the condition in the loop body to be true one iteration early, which
    would be incorrect for other users.
    
    Fixes https://github.com/llvm/llvm-project/issues/140444.

diff --git a/llvm/lib/Transforms/Utils/LoopPeel.cpp b/llvm/lib/Transforms/Utils/LoopPeel.cpp
index 646354bba9df..4eaa3c971437 100644
--- a/llvm/lib/Transforms/Utils/LoopPeel.cpp
+++ b/llvm/lib/Transforms/Utils/LoopPeel.cpp
@@ -343,12 +343,12 @@ bool llvm::canPeelLastIteration(const Loop &L, ScalarEvolution &SE) {
   // codegen. For now, it must
   // * exit via the latch,
   // * the exit condition must be a NE/EQ compare of an induction with step
-  // of 1.
+  // of 1 and must only be used by the exiting branch.
   BasicBlock *Latch = L.getLoopLatch();
   return Latch && Latch == L.getExitingBlock() &&
          match(Latch->getTerminator(),
-               m_Br(m_ICmp(Pred, m_Value(Inc), m_Value()), m_BasicBlock(Succ1),
-                    m_BasicBlock(Succ2))) &&
+               m_Br(m_OneUse(m_ICmp(Pred, m_Value(Inc), m_Value())),
+                    m_BasicBlock(Succ1), m_BasicBlock(Succ2))) &&
          ((Pred == CmpInst::ICMP_EQ && Succ2 == L.getHeader()) ||
           (Pred == CmpInst::ICMP_NE && Succ1 == L.getHeader())) &&
          isa<SCEVAddRecExpr>(SE.getSCEV(Inc)) &&


Regression tests:
file: llvm/test/Transforms/LoopUnroll/peel-last-iteration.ll
commands: opt -p loop-unroll -unroll-full-max-count=0 -S %s
tests: test_name: exit_condition_has_other_loop_users
test_body: define void @exit_condition_has_other_loop_users() {
entry:
  br label %loop.header

loop.header:                                      ; preds = %loop.latch, %entry
  %iv = phi i16 [ 0, %entry ], [ %iv.next, %loop.latch ]
  %iv.next = add i16 %iv, 1
  %ec = icmp eq i16 %iv, 100
  br i1 %ec, label %loop.latch, label %then

then:                                             ; preds = %loop.header
  call void @foo(i32 10)
  br label %loop.latch

loop.latch:                                       ; preds = %then, %loop.header
  call void @foo(i32 20)
  br i1 %ec, label %exit, label %loop.header

exit:                                             ; preds = %loop.latch
  ret void
}

declare void @foo(i32)