[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