[llvm] [LoopUnroll] Do not copy !llvm.loop from latch to non-latch (PR #165635)

Joel E. Denny via llvm-commits llvm-commits at lists.llvm.org
Wed Oct 29 18:08:02 PDT 2025


https://github.com/jdenny-ornl created https://github.com/llvm/llvm-project/pull/165635

When LoopUnroll copies the original loop's latch to the corresponding non-latch branch in an unrolled iteration, any `!llvm.loop` is copied along with it, but `!llvm.loop` is useless and misleading there.  This patch discards it.

e06831a3b29d did the same for LoopPeel.

>From 705d54e557e1c42218835e4595f72b07e938cb88 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Wed, 29 Oct 2025 20:43:32 -0400
Subject: [PATCH] [LoopUnroll] Do not copy !llvm.loop from latch to non-latch

When LoopUnroll copies the original loop's latch to the corresponding
non-latch branch in an unrolled iteration, any `!llvm.loop` is copied
along with it, but `!llvm.loop` is useless and misleading there.  This
patch discards it.

e06831a3b29d did the same for LoopPeel.
---
 llvm/lib/Transforms/Utils/LoopUnroll.cpp      |  6 ++
 .../LoopUnroll/convergent.controlled.ll       | 18 ++--
 llvm/test/Transforms/LoopUnroll/followup.ll   |  8 +-
 llvm/test/Transforms/LoopUnroll/pr131465.ll   |  8 +-
 .../unroll-dont-copy-latch-loop-id.ll         | 90 +++++++++++++++++++
 .../Transforms/LoopUnroll/unroll-loads-cse.ll |  8 +-
 6 files changed, 115 insertions(+), 23 deletions(-)
 create mode 100644 llvm/test/Transforms/LoopUnroll/unroll-dont-copy-latch-loop-id.ll

diff --git a/llvm/lib/Transforms/Utils/LoopUnroll.cpp b/llvm/lib/Transforms/Utils/LoopUnroll.cpp
index 4fe736ac29b0a..8bbc857715d27 100644
--- a/llvm/lib/Transforms/Utils/LoopUnroll.cpp
+++ b/llvm/lib/Transforms/Utils/LoopUnroll.cpp
@@ -919,6 +919,12 @@ llvm::UnrollLoop(Loop *L, UnrollLoopOptions ULO, LoopInfo *LI,
     Latches[i]->getTerminator()->replaceSuccessorWith(Headers[i], Headers[j]);
   }
 
+  // Remove loop metadata copied from the original loop latch to branches that
+  // are no longer latches.
+  for (unsigned I = 0, E = Latches.size() - (CompletelyUnroll ? 0 : 1); I < E;
+       ++I)
+    Latches[I]->getTerminator()->setMetadata(LLVMContext::MD_loop, nullptr);
+
   // Update dominators of blocks we might reach through exits.
   // Immediate dominator of such block might change, because we add more
   // routes which can lead to the exit: we can now reach it from the copied
diff --git a/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll b/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll
index 6e600d2a659da..5dc613e733f00 100644
--- a/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll
+++ b/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll
@@ -491,41 +491,41 @@ define i32 @unroll_nest(i32 %n, i1 %cond) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    br label [[L3:%.*]]
 ; CHECK:       l3:
-; CHECK-NEXT:    br label [[L2:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br label [[L2:%.*]]
 ; CHECK:       l2:
 ; CHECK-NEXT:    [[TOK_L2:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2]]) ]
-; CHECK-NEXT:    br i1 [[COND:%.*]], label [[L2_1:%.*]], label [[LATCH:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br i1 [[COND:%.*]], label [[L2_1:%.*]], label [[LATCH:%.*]]
 ; CHECK:       l2.1:
 ; CHECK-NEXT:    [[TOK_L2_1:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_1]]) ]
 ; CHECK-NEXT:    br i1 [[COND]], label [[L2]], label [[LATCH]], !llvm.loop [[LOOP9:![0-9]+]]
 ; CHECK:       latch:
-; CHECK-NEXT:    br label [[L2_12:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br label [[L2_12:%.*]]
 ; CHECK:       l2.12:
 ; CHECK-NEXT:    [[TOK_L2_11:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_11]]) ]
-; CHECK-NEXT:    br i1 [[COND]], label [[L2_1_1:%.*]], label [[LATCH_1:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br i1 [[COND]], label [[L2_1_1:%.*]], label [[LATCH_1:%.*]]
 ; CHECK:       l2.1.1:
 ; CHECK-NEXT:    [[TOK_L2_1_1:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_1_1]]) ]
 ; CHECK-NEXT:    br i1 [[COND]], label [[L2_12]], label [[LATCH_1]], !llvm.loop [[LOOP9]]
 ; CHECK:       latch.1:
-; CHECK-NEXT:    br label [[L2_2:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br label [[L2_2:%.*]]
 ; CHECK:       l2.2:
 ; CHECK-NEXT:    [[TOK_L2_2:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_2]]) ]
-; CHECK-NEXT:    br i1 [[COND]], label [[L2_1_2:%.*]], label [[LATCH_2:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br i1 [[COND]], label [[L2_1_2:%.*]], label [[LATCH_2:%.*]]
 ; CHECK:       l2.1.2:
 ; CHECK-NEXT:    [[TOK_L2_1_2:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_1_2]]) ]
 ; CHECK-NEXT:    br i1 [[COND]], label [[L2_2]], label [[LATCH_2]], !llvm.loop [[LOOP9]]
 ; CHECK:       latch.2:
-; CHECK-NEXT:    br label [[L2_3:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br label [[L2_3:%.*]]
 ; CHECK:       l2.3:
 ; CHECK-NEXT:    [[TOK_L2_3:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_3]]) ]
-; CHECK-NEXT:    br i1 [[COND]], label [[L2_1_3:%.*]], label [[LATCH_3:%.*]], !llvm.loop [[LOOP4]]
+; CHECK-NEXT:    br i1 [[COND]], label [[L2_1_3:%.*]], label [[LATCH_3:%.*]]
 ; CHECK:       l2.1.3:
 ; CHECK-NEXT:    [[TOK_L2_1_3:%.*]] = call token @llvm.experimental.convergence.anchor()
 ; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_L2_1_3]]) ]
@@ -541,7 +541,7 @@ l3:
   %tok.loop = call token @llvm.experimental.convergence.anchor()
   %inc = add nsw i32 %x.0, 1
   %exitcond = icmp eq i32 %inc, 4
-  br label %l2, !llvm.loop !1
+  br label %l2
 
 l2:
   %tok.l2 = call token @llvm.experimental.convergence.anchor()
diff --git a/llvm/test/Transforms/LoopUnroll/followup.ll b/llvm/test/Transforms/LoopUnroll/followup.ll
index 051e43d52b3be..b6561781d29ca 100644
--- a/llvm/test/Transforms/LoopUnroll/followup.ll
+++ b/llvm/test/Transforms/LoopUnroll/followup.ll
@@ -36,11 +36,11 @@ for.end:                                          ; preds = %for.body, %entry
 ; COMMON-LABEL: @test(
 
 
-; COUNT: br i1 %exitcond.1, label %for.end.loopexit, label %for.body, !llvm.loop ![[LOOP:[0-9]+]]
+; COUNT: br i1 %exitcond.1, label %for.end.loopexit, label %for.body, !llvm.loop ![[#LOOP:]]
 
-; COUNT: ![[FOLLOWUP_ALL:[0-9]+]] = !{!"FollowupAll"}
-; COUNT: ![[FOLLOWUP_UNROLLED:[0-9]+]] = !{!"FollowupUnrolled"}
-; COUNT: ![[LOOP]] = distinct !{![[LOOP]], ![[FOLLOWUP_ALL]], ![[FOLLOWUP_UNROLLED]]}
+; COUNT: ![[#LOOP]] = distinct !{![[#LOOP]], ![[#FOLLOWUP_ALL:]], ![[#FOLLOWUP_UNROLLED:]]}
+; COUNT: ![[#FOLLOWUP_ALL]] = !{!"FollowupAll"}
+; COUNT: ![[#FOLLOWUP_UNROLLED]] = !{!"FollowupUnrolled"}
 
 
 ; EPILOG: br i1 %niter.ncmp.7, label %for.end.loopexit.unr-lcssa, label %for.body, !llvm.loop ![[LOOP_0:[0-9]+]]
diff --git a/llvm/test/Transforms/LoopUnroll/pr131465.ll b/llvm/test/Transforms/LoopUnroll/pr131465.ll
index 643b020c6c110..60bea6a4d9043 100644
--- a/llvm/test/Transforms/LoopUnroll/pr131465.ll
+++ b/llvm/test/Transforms/LoopUnroll/pr131465.ll
@@ -11,11 +11,11 @@ define i32 @pr131465(i1 %x) mustprogress {
 ; CHECK-NEXT:    [[INDVAR:%.*]] = phi i32 [ 2, %[[ENTRY]] ], [ [[NEXT_1:%.*]], %[[FOR_BODY_1:.*]] ]
 ; CHECK-NEXT:    [[NEXT:%.*]] = add nsw i32 [[INDVAR]], [[INC]]
 ; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp eq i32 [[NEXT]], 2
-; CHECK-NEXT:    br i1 [[EXITCOND]], label %[[FOR_END:.*]], label %[[FOR_BODY_1]], !llvm.loop [[LOOP0:![0-9]+]]
+; CHECK-NEXT:    br i1 [[EXITCOND]], label %[[FOR_END:.*]], label %[[FOR_BODY_1]]
 ; CHECK:       [[FOR_BODY_1]]:
 ; CHECK-NEXT:    [[NEXT_1]] = add nsw i32 [[NEXT]], [[INC]]
 ; CHECK-NEXT:    [[EXITCOND_1:%.*]] = icmp eq i32 [[NEXT_1]], 2
-; CHECK-NEXT:    br i1 [[EXITCOND_1]], label %[[FOR_END]], label %[[FOR_BODY]], !llvm.loop [[LOOP2:![0-9]+]]
+; CHECK-NEXT:    br i1 [[EXITCOND_1]], label %[[FOR_END]], label %[[FOR_BODY]], !llvm.loop [[LOOP0:![0-9]+]]
 ; CHECK:       [[FOR_END]]:
 ; CHECK-NEXT:    ret i32 0
 ;
@@ -37,7 +37,5 @@ for.end:
 !0 = !{!0, !{!"llvm.loop.unroll.count", i32 2}}
 ;.
 ; CHECK: [[LOOP0]] = distinct !{[[LOOP0]], [[META1:![0-9]+]]}
-; CHECK: [[META1]] = !{!"llvm.loop.unroll.count", i32 2}
-; CHECK: [[LOOP2]] = distinct !{[[LOOP2]], [[META3:![0-9]+]]}
-; CHECK: [[META3]] = !{!"llvm.loop.unroll.disable"}
+; CHECK: [[META1]] = !{!"llvm.loop.unroll.disable"}
 ;.
diff --git a/llvm/test/Transforms/LoopUnroll/unroll-dont-copy-latch-loop-id.ll b/llvm/test/Transforms/LoopUnroll/unroll-dont-copy-latch-loop-id.ll
new file mode 100644
index 0000000000000..191ebb023b7c1
--- /dev/null
+++ b/llvm/test/Transforms/LoopUnroll/unroll-dont-copy-latch-loop-id.ll
@@ -0,0 +1,90 @@
+; Check that !llvm.loop is not copied from the original loop's latch to the
+; corresponding non-latch branch in any unrolled iteration.
+
+; The -implicit-check-not options make sure that no additional label or
+; !llvm.loop shows up.
+; DEFINE: %{unroll} = opt < %s -passes=loop-unroll -S
+; DEFINE: %{fc} = FileCheck %s \
+; DEFINE:     -implicit-check-not='{{^[^ ;]*:}}' \
+; DEFINE:     -implicit-check-not='!llvm.loop' \
+; DEFINE:     -check-prefixes
+
+; Check partial unroll: only the unrolled loop's latch has !llvm.loop.
+; RUN: %{unroll} -unroll-count=3 | %{fc} ALL,UR,UR3
+
+; Check complete unroll: no !llvm.loop remains because no loop remains.
+; RUN: %{unroll} -unroll-count=4 | %{fc} ALL,UR,UR4
+
+; Check remainder: both loops have a !llvm.loop.
+; DEFINE: %{rt} = %{unroll} -unroll-count=3 -unroll-runtime
+; RUN: %{rt} -unroll-runtime-epilog=true | %{fc} ALL,RT,EPILOG
+; RUN: %{rt} -unroll-runtime-epilog=false | %{fc} ALL,RT,PROLOG
+
+;    ALL: define void @test(i32 %n) {
+;    ALL: entry:
+;     UR:   br label %body
+; EPILOG:   br i1 %{{.*}}, label %body.epil.preheader, label %entry.new
+; PROLOG:   br i1 %{{.*}}, label %body.prol.preheader, label %body.prol.loopexit
+; PROLOG: body.prol.preheader:
+; PROLOG:   br label %body.prol
+; PROLOG: body.prol:
+; PROLOG:   br i1 %{{.*}}, label %body.prol, label %body.prol.loopexit.unr-lcssa, !llvm.loop !0
+; PROLOG: body.prol.loopexit.unr-lcssa:
+; PROLOG:   br label %body.prol.loopexit
+; PROLOG: body.prol.loopexit:
+; PROLOG:   br i1 %{{.*}}, label %exit, label %entry.new
+;     RT: entry.new:
+;     RT:   br label %body
+;    ALL: body:
+;     UR:   br i1 %c, label %body.1, label %exit
+; EPILOG:   br i1 %{{.*}}, label %body, label %exit.unr-lcssa, !llvm.loop !0
+; PROLOG:   br i1 %{{.*}}, label %body, label %exit.unr-lcssa, !llvm.loop !2
+;     UR: body.1:
+;     UR:   br i1 %c.1, label %body.2, label %exit
+;     UR: body.2:
+;    UR3:   br i1 %c.2, label %body, label %exit, !llvm.loop !0
+;    UR4:   br i1 %c.2, label %body.3, label %exit
+;    UR4: body.3:
+;    UR4:   br label %exit
+;     RT: exit.unr-lcssa:
+; EPILOG:   br i1 {{.*}}, label %body.epil.preheader, label %exit
+; PROLOG:   br label %exit
+; EPILOG: body.epil.preheader:
+; EPILOG:   br label %body.epil
+; EPILOG: body.epil:
+; EPILOG:   br i1 {{.*}}, label %body.epil, label %exit.epilog-lcssa, !llvm.loop !3
+; EPILOG: exit.epilog-lcssa:
+; EPILOG:   br label %exit
+;    ALL: exit:
+;    ALL:   ret void
+;    ALL: }
+define void @test(i32 %n) {
+entry:
+  %max = call i32 @llvm.umin.i32(i32 %n, i32 3)
+  br label %body
+
+body:
+  %i = phi i32 [ 0, %entry ], [ %inc, %body ]
+  %inc = add i32 %i, 1
+  %c = icmp ult i32 %i, %max
+  br i1 %c, label %body, label %exit, !llvm.loop !0
+
+exit:
+  ret void
+}
+
+; UR3: !0 = distinct !{!0, !1, !2}
+; UR3: !1 = !{!"copied"}
+; UR3: !2 = !{!"llvm.loop.unroll.disable"}
+;
+; EPILOG: !0 = distinct !{!0, !1, !2}
+; EPILOG: !1 = !{!"copied"}
+; EPILOG: !2 = !{!"llvm.loop.unroll.disable"}
+; EPILOG: !3 = distinct !{!3, !2}
+;
+; PROLOG: !0 = distinct !{!0, !1}
+; PROLOG: !1 = !{!"llvm.loop.unroll.disable"}
+; PROLOG: !2 = distinct !{!2, !3, !1}
+; PROLOG: !3 = !{!"copied"}
+!0 = distinct !{!0, !1}
+!1 = !{!"copied"}
diff --git a/llvm/test/Transforms/LoopUnroll/unroll-loads-cse.ll b/llvm/test/Transforms/LoopUnroll/unroll-loads-cse.ll
index f85aac75539c6..77dd3429b684e 100644
--- a/llvm/test/Transforms/LoopUnroll/unroll-loads-cse.ll
+++ b/llvm/test/Transforms/LoopUnroll/unroll-loads-cse.ll
@@ -419,7 +419,7 @@ define void @loop_body_with_dead_blocks(ptr %src) {
 ; CHECK-NEXT:    call void @foo()
 ; CHECK-NEXT:    [[L_2:%.*]] = load i32, ptr [[SRC]], align 8
 ; CHECK-NEXT:    [[C_2:%.*]] = icmp eq i32 [[L_2]], 1
-; CHECK-NEXT:    br i1 [[C_2]], label [[EXIT:%.*]], label [[LOOP_HEADER_1:%.*]], !llvm.loop [[LOOP7:![0-9]+]]
+; CHECK-NEXT:    br i1 [[C_2]], label [[EXIT:%.*]], label [[LOOP_HEADER_1:%.*]]
 ; CHECK:       loop.header.1:
 ; CHECK-NEXT:    br label [[LOOP_BB_1:%.*]]
 ; CHECK:       loop.bb.1:
@@ -429,7 +429,7 @@ define void @loop_body_with_dead_blocks(ptr %src) {
 ; CHECK-NEXT:    call void @foo()
 ; CHECK-NEXT:    [[L_2_1:%.*]] = load i32, ptr [[SRC]], align 8
 ; CHECK-NEXT:    [[C_2_1:%.*]] = icmp eq i32 [[L_2_1]], 1
-; CHECK-NEXT:    br i1 [[C_2_1]], label [[EXIT]], label [[LOOP_HEADER]], !llvm.loop [[LOOP9:![0-9]+]]
+; CHECK-NEXT:    br i1 [[C_2_1]], label [[EXIT]], label [[LOOP_HEADER]], !llvm.loop [[LOOP7:![0-9]+]]
 ; CHECK:       exit:
 ; CHECK-NEXT:    ret void
 ;
@@ -471,7 +471,5 @@ exit:
 ; CHECK: [[LOOP4]] = distinct !{[[LOOP4]], [[META1]], [[META2]]}
 ; CHECK: [[LOOP5]] = distinct !{[[LOOP5]], [[META1]], [[META2]]}
 ; CHECK: [[LOOP6]] = distinct !{[[LOOP6]], [[META1]], [[META2]]}
-; CHECK: [[LOOP7]] = distinct !{[[LOOP7]], [[META1]], [[META8:![0-9]+]]}
-; CHECK: [[META8]] = !{!"llvm.loop.unroll.count", i32 2}
-; CHECK: [[LOOP9]] = distinct !{[[LOOP9]], [[META1]], [[META2]]}
+; CHECK: [[LOOP7]] = distinct !{[[LOOP7]], [[META1]], [[META2]]}
 ;.



More information about the llvm-commits mailing list