[llvm] [Analysis] Ignore convergence tokens in dead branches in CodeMetrics (PR #193590)

Finn Plummer via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 22 13:52:09 PDT 2026


https://github.com/inbelic created https://github.com/llvm/llvm-project/pull/193590

In `extendsConvergenceOutsideLoop`, skip users whose parent block is terminated by unreachable, as these represent dead branches. This prevents convergence control tokens in dead code from incorrectly marking a loop as having extended convergence, which could block loop unrolling.

Assisted by: Claude Opus 4.6

>From ccfea43f557ded486f3764c1d5968168b04456e9 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 22 Apr 2026 19:20:46 +0000
Subject: [PATCH] [Analysis] Ignore convergence tokens in dead branches in
 CodeMetrics

In `extendsConvergenceOutsideLoop`, skip users whose parent block is
terminated by unreachable, as these represent dead branches. This
prevents convergence control tokens in dead code from incorrectly
marking a loop as having extended convergence, which could block
loop unrolling.
---
 llvm/lib/Analysis/CodeMetrics.cpp             |  8 ++-
 .../LoopUnroll/convergent.controlled.ll       | 58 +++++++++++++++++++
 2 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/CodeMetrics.cpp b/llvm/lib/Analysis/CodeMetrics.cpp
index ea67b526423bf..21f5ff7319ceb 100644
--- a/llvm/lib/Analysis/CodeMetrics.cpp
+++ b/llvm/lib/Analysis/CodeMetrics.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/TargetTransformInfo.h"
 #include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/InstructionCost.h"
@@ -118,7 +119,12 @@ static bool extendsConvergenceOutsideLoop(const Instruction &I, const Loop *L) {
   if (!isa<ConvergenceControlInst>(I))
     return false;
   for (const auto *U : I.users()) {
-    if (!L->contains(cast<Instruction>(U)))
+    const auto *UserInst = cast<Instruction>(U);
+    // Ignore users in dead branches, identified by blocks terminated with
+    // unreachable.
+    if (isa<UnreachableInst>(UserInst->getParent()->getTerminator()))
+      continue;
+    if (!L->contains(UserInst))
       return true;
   }
   return false;
diff --git a/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll b/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll
index 5dc613e733f00..6836aea1ff61f 100644
--- a/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll
+++ b/llvm/test/Transforms/LoopUnroll/convergent.controlled.ll
@@ -555,6 +555,64 @@ exit:
   ret i32 0
 }
 
+; A convergence token defined in the loop is used outside the loop, but only
+; in a dead branch (block terminated by unreachable). This should not prevent
+; unrolling.
+define i32 @extended_loop_dead_branch(i32 %n) {
+; CHECK-LABEL: @extended_loop_dead_branch(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = freeze i32 [[N:%.*]]
+; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[TMP0]], -1
+; CHECK-NEXT:    [[XTRAITER:%.*]] = and i32 [[TMP0]], 1
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 1
+; CHECK-NEXT:    br i1 [[TMP2]], label [[L3_EPIL_PREHEADER:%.*]], label [[ENTRY_NEW:%.*]]
+; CHECK:       entry.new:
+; CHECK-NEXT:    [[UNROLL_ITER:%.*]] = sub i32 [[TMP0]], [[XTRAITER]]
+; CHECK-NEXT:    br label [[L3:%.*]], !llvm.loop [[LOOP4]]
+; CHECK:       l3:
+; CHECK-NEXT:    [[X_0:%.*]] = phi i32 [ 0, [[ENTRY_NEW]] ], [ [[INC_1:%.*]], [[L3]] ]
+; CHECK-NEXT:    [[NITER:%.*]] = phi i32 [ 0, [[ENTRY_NEW]] ], [ [[NITER_NEXT_1:%.*]], [[L3]] ]
+; CHECK-NEXT:    [[TOK_LOOP:%.*]] = call token @llvm.experimental.convergence.anchor()
+; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_LOOP]]) ]
+; CHECK-NEXT:    [[TOK_LOOP_1:%.*]] = call token @llvm.experimental.convergence.anchor()
+; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_LOOP_1]]) ]
+; CHECK-NEXT:    [[INC_1]] = add nsw i32 [[X_0]], 2
+; CHECK-NEXT:    [[NITER_NEXT_1]] = add i32 [[NITER]], 2
+; CHECK-NEXT:    [[NITER_NCMP_1:%.*]] = icmp eq i32 [[NITER_NEXT_1]], [[UNROLL_ITER]]
+; CHECK-NEXT:    br i1 [[NITER_NCMP_1]], label [[EXIT_UNR_LCSSA:%.*]], label [[L3]], !llvm.loop [[LOOP10:![0-9]+]]
+; CHECK:       exit.unr-lcssa:
+; CHECK-NEXT:    [[LCMP_MOD:%.*]] = icmp ne i32 [[XTRAITER]], 0
+; CHECK-NEXT:    br i1 [[LCMP_MOD]], label [[L3_EPIL_PREHEADER]], label [[EXIT:%.*]]
+; CHECK:       l3.epil.preheader:
+; CHECK-NEXT:    [[LCMP_MOD1:%.*]] = icmp ne i32 [[XTRAITER]], 0
+; CHECK-NEXT:    call void @llvm.assume(i1 [[LCMP_MOD1]])
+; CHECK-NEXT:    br label [[L3_EPIL:%.*]]
+; CHECK:       l3.epil:
+; CHECK-NEXT:    [[TOK_LOOP_EPIL:%.*]] = call token @llvm.experimental.convergence.anchor()
+; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK_LOOP_EPIL]]) ]
+; CHECK-NEXT:    br label [[EXIT]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %l3, !llvm.loop !1
+
+l3:
+  %x.0 = phi i32 [ 0, %entry ], [ %inc, %l3 ]
+  %tok.loop = call token @llvm.experimental.convergence.anchor()
+  call void @f() [ "convergencectrl"(token %tok.loop) ]
+  %inc = add nsw i32 %x.0, 1
+  %exitcond = icmp eq i32 %inc, %n
+  br i1 %exitcond, label %exit, label %l3, !llvm.loop !1
+
+exit:
+  ret i32 0
+
+dead:
+  call void @f() [ "convergencectrl"(token %tok.loop) ]
+  unreachable
+}
+
 declare token @llvm.experimental.convergence.anchor()
 declare token @llvm.experimental.convergence.loop()
 



More information about the llvm-commits mailing list