[llvm] [LoopFusion] Do not fuse loops containing convergent operations (PR #203460)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 22:41:23 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Madhur Amilkanthwar (madhur13490)

<details>
<summary>Changes</summary>

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.

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


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/LoopFuse.cpp (+11) 
- (added) llvm/test/Transforms/LoopFusion/cannot_fuse_convergent.ll (+42) 


``````````diff
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
+}

``````````

</details>


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


More information about the llvm-commits mailing list