[llvm] [LoopFusion] Do not fuse loops containing convergent operations (PR #203460)
Madhur Amilkanthwar via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 11 22:40:40 PDT 2026
https://github.com/madhur13490 created https://github.com/llvm/llvm-project/pull/203460
A convergent operation may only run where the set of threads executing it is unchanged. Fusion relocates the operation into a different loop and interleaves it with the other loop's body, changing its control-flow context, which is invalid for convergent ops.
Reject a fusion candidate whose body contains a convergent call, mirroring the existing throw/volatile/atomic checks.
>From 0ed71ceb6f61f92c383b6c97fc0ca7ee0323e1af Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Fri, 5 Jun 2026 06:19:38 -0700
Subject: [PATCH] [LoopFusion] Do not fuse loops containing convergent
operations
A convergent operation may only run where the set of threads executing it
is unchanged. Fusion relocates the operation into a different loop and
interleaves it with the other loop's body, changing its control-flow
context, which is invalid for convergent ops (LoopDistribute, the inverse
transform, already guards this via LoopAccessInfo::hasConvergentOp).
Reject a fusion candidate whose body contains a convergent call, mirroring
the existing throw/volatile/atomic checks, and add a regression test.
---
llvm/lib/Transforms/Scalar/LoopFuse.cpp | 11 +++++
.../LoopFusion/cannot_fuse_convergent.ll | 42 +++++++++++++++++++
2 files changed, 53 insertions(+)
create mode 100644 llvm/test/Transforms/LoopFusion/cannot_fuse_convergent.ll
diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index b5126f9f3b704..97566d7315359 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -76,6 +76,7 @@ STATISTIC(AddressTakenBB, "Basic block has address taken");
STATISTIC(MayThrowException, "Loop may throw an exception");
STATISTIC(ContainsVolatileAccess, "Loop contains a volatile access");
STATISTIC(ContainsAtomicAccess, "Loop contains an atomic access");
+STATISTIC(ContainsConvergentOp, "Loop contains a convergent operation");
STATISTIC(NotSimplifiedForm, "Loop is not in simplified form");
STATISTIC(InvalidDependencies, "Dependencies prevent fusion");
STATISTIC(UnknownTripCount, "Loop has unknown trip count");
@@ -203,6 +204,16 @@ struct FusionCandidate {
"Loop contains an atomic access");
return;
}
+ // A convergent operation requires the set of threads executing it to be
+ // unchanged; fusion moves it into a different loop, so reject the
+ // candidate. See LangRef on the 'convergent' attribute.
+ if (const auto *CB = dyn_cast<CallBase>(&I); CB && CB->isConvergent()) {
+ invalidate();
+ ++ContainsConvergentOp;
+ reportInvalidCandidate("ContainsConvergentOp",
+ "Loop contains a convergent operation");
+ return;
+ }
if (I.mayWriteToMemory())
MemWrites.push_back(&I);
if (I.mayReadFromMemory())
diff --git a/llvm/test/Transforms/LoopFusion/cannot_fuse_convergent.ll b/llvm/test/Transforms/LoopFusion/cannot_fuse_convergent.ll
new file mode 100644
index 0000000000000..453a24aae26d4
--- /dev/null
+++ b/llvm/test/Transforms/LoopFusion/cannot_fuse_convergent.ll
@@ -0,0 +1,42 @@
+; RUN: opt -S -passes=loop-simplify,loop-fusion \
+; RUN: -pass-remarks-analysis=loop-fusion -disable-output < %s 2>&1 \
+; RUN: | FileCheck %s
+; REQUIRES: asserts
+
+; A convergent operation requires the set of threads that execute it to be
+; unchanged. Fusion would move it into a different loop, so a loop containing
+; one is not a fusion candidate.
+
+; CHECK: [convergent_op]: Loop is not a candidate for fusion: Loop contains a convergent operation
+
+declare void @bar() nounwind convergent memory(none)
+
+define void @convergent_op(ptr noalias %arg) {
+entry:
+ br label %for.body1
+
+for.body1: ; preds = %entry, %for.body1
+ %i1 = phi i64 [ 0, %entry ], [ %inc1, %for.body1 ]
+ %gep1 = getelementptr inbounds i32, ptr %arg, i64 %i1
+ %v1 = trunc i64 %i1 to i32
+ store i32 %v1, ptr %gep1, align 4
+ call void @bar()
+ %inc1 = add nuw nsw i64 %i1, 1
+ %cond1 = icmp ne i64 %inc1, 100
+ br i1 %cond1, label %for.body1, label %for.body2.preheader
+
+for.body2.preheader: ; preds = %for.body1
+ br label %for.body2
+
+for.body2: ; preds = %for.body2.preheader, %for.body2
+ %i2 = phi i64 [ 0, %for.body2.preheader ], [ %inc2, %for.body2 ]
+ %gep2 = getelementptr inbounds i32, ptr %arg, i64 %i2
+ %v2 = trunc i64 %i2 to i32
+ store i32 %v2, ptr %gep2, align 4
+ %inc2 = add nuw nsw i64 %i2, 1
+ %cond2 = icmp ne i64 %inc2, 100
+ br i1 %cond2, label %for.body2, label %exit
+
+exit: ; preds = %for.body2
+ ret void
+}
More information about the llvm-commits
mailing list