[llvm] [LoopDeletion] Don't delete a loop that defines a live-out token (PR #223119)

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 21:28:32 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Timur Baidusenov (bai-tim)

<details>
<summary>Changes</summary>

Fixes #<!-- -->156651.

---
Full diff: https://github.com/llvm/llvm-project/pull/223119.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/LoopDeletion.cpp (+27) 
- (added) llvm/test/Transforms/LoopDeletion/live-out-token.ll (+102) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/LoopDeletion.cpp b/llvm/lib/Transforms/Scalar/LoopDeletion.cpp
index 6079e8de68519..ab49dc3cde3cc 100644
--- a/llvm/lib/Transforms/Scalar/LoopDeletion.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopDeletion.cpp
@@ -419,6 +419,23 @@ breakBackedgeIfNotTaken(Loop *L, DominatorTree &DT, ScalarEvolution &SE,
   return LoopDeletionResult::Deleted;
 }
 
+/// Return true if the loop defines a token-like value that is used outside of
+/// it. Such a value cannot be replaced by a PHI node in the exit block, so
+/// LCSSA form leaves the use where it is.
+static bool hasLiveOutToken(const Loop *L) {
+  for (const BasicBlock *BB : L->blocks())
+    for (const Instruction &I : *BB) {
+      if (!I.getType()->isTokenLikeTy())
+        continue;
+      // A token-like value cannot be an operand of a PHI node, so the block of
+      // the user is where the use happens.
+      for (const User *U : I.users())
+        if (!L->contains(cast<Instruction>(U)->getParent()))
+          return true;
+    }
+  return false;
+}
+
 /// Remove a loop if it is dead.
 ///
 /// A loop is considered dead either if it does not impact the observable
@@ -459,6 +476,16 @@ static LoopDeletionResult deleteLoopIfDead(Loop *L, DominatorTree &DT,
     return LoopDeletionResult::Unmodified;
   }
 
+  // Deleting the loop erases the instructions in it. The checks below rely on
+  // LCSSA form to find the values that are used after the loop, but token-like
+  // values are exempt from it because they cannot be used by a PHI node. Bail
+  // out on them here, or such a use would be left behind: dangling if it is
+  // reachable, and set to a token poison that no intrinsic produced if not.
+  if (hasLiveOutToken(L)) {
+    LLVM_DEBUG(dbgs() << "Cannot delete loop with a live-out token value.\n");
+    return LoopDeletionResult::Unmodified;
+  }
+
   if (ExitBlock && isLoopNeverExecuted(L)) {
     LLVM_DEBUG(dbgs() << "Loop is proven to never execute, delete it!\n");
     // We need to forget the loop before setting the incoming values of the exit
diff --git a/llvm/test/Transforms/LoopDeletion/live-out-token.ll b/llvm/test/Transforms/LoopDeletion/live-out-token.ll
new file mode 100644
index 0000000000000..008070408af68
--- /dev/null
+++ b/llvm/test/Transforms/LoopDeletion/live-out-token.ll
@@ -0,0 +1,102 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=loop-deletion < %s | FileCheck %s
+
+; A token-like value cannot be used by a PHI node, so LCSSA form does not
+; report it as live out of the loop. Deleting the loop would leave the use in
+; %exit dangling.
+
+declare void @f() convergent
+declare token @llvm.experimental.convergence.anchor()
+
+define i32 @live_out_token(i32 %n) {
+; CHECK-LABEL: define i32 @live_out_token(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    [[TOK:%.*]] = call token @llvm.experimental.convergence.anchor()
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i32 [[IV]], 1
+; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp eq i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    br i1 [[EXITCOND]], label %[[EXIT:.*]], label %[[LOOP]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK]]) ]
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+  %tok = call token @llvm.experimental.convergence.anchor()
+  %iv.next = add nsw i32 %iv, 1
+  %exitcond = icmp eq i32 %iv.next, %n
+  br i1 %exitcond, label %exit, label %loop
+
+exit:
+  call void @f() [ "convergencectrl"(token %tok) ]
+  ret i32 0
+}
+
+; The token is only used inside the loop, so the loop can still be deleted.
+
+define i32 @token_used_in_loop(i32 %n) {
+; CHECK-LABEL: define i32 @token_used_in_loop(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+  %tok = call token @llvm.experimental.convergence.anchor()
+  %iv.next = add nsw i32 %iv, 1
+  %exitcond = icmp eq i32 %iv.next, %n
+  br i1 %exitcond, label %exit, label %loop
+
+exit:
+  ret i32 0
+}
+
+; The use is in an unreachable block, so the loop looks dead. Deleting it would
+; set the operand of the convergencectrl bundle to a token poison, which no
+; convergence control intrinsic produced.
+
+define i32 @unreachable_token_use(i32 %n) {
+; CHECK-LABEL: define i32 @unreachable_token_use(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    [[TOK:%.*]] = call token @llvm.experimental.convergence.anchor()
+; CHECK-NEXT:    [[IV_NEXT]] = add nsw i32 [[IV]], 1
+; CHECK-NEXT:    [[EXITCOND:%.*]] = icmp eq i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    br i1 [[EXITCOND]], label %[[EXIT:.*]], label %[[LOOP]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret i32 0
+; CHECK:       [[DEAD:.*:]]
+; CHECK-NEXT:    call void @f() [ "convergencectrl"(token [[TOK]]) ]
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+  %tok = call token @llvm.experimental.convergence.anchor()
+  %iv.next = add nsw i32 %iv, 1
+  %exitcond = icmp eq i32 %iv.next, %n
+  br i1 %exitcond, label %exit, label %loop
+
+exit:
+  ret i32 0
+
+dead:
+  call void @f() [ "convergencectrl"(token %tok) ]
+  ret i32 0
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/223119


More information about the llvm-commits mailing list