[llvm] [JumpThreading] Don't thread into blocks with convergent operations (PR #213870)

via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 4 01:23:09 PDT 2026


https://github.com/39ali created https://github.com/llvm/llvm-project/pull/213870

Jump threading refuses to duplicate a block containing convergent calls
(`getJumpThreadDuplicationCost` returns infinite cost), but nothing prevents
it from rerouting an edge so that a block containing convergent operations
gains a new entry edge from a different control-flow region: `threadEdge` and
`threadThroughTwoBasicBlocks` duplicate only the (cost-checked) threaded
blocks and then wire the clones directly into `SuccBB`, whose contents are
never examined.

For convergent operations the destination's entry edges are semantically
load-bearing: targets using the token-less `convergent` attribute (NVPTX)
derive warp reconvergence points from the CFG merge structure. The new edge
can bypass the merge point that was the only valid reconvergence location
between a divergent branch and the convergent operation, so the operation
executes with a partially converged warp.

This miscompiles real code: Triton kernels through plain
`opt -passes='default<O3>'` end up executing `llvm.nvvm.ldmatrix.sync` with a
partially converged warp.

Fix: refuse `tryThreadEdge` / `maybethreadThroughTwoBasicBlocks` when the
threading destination contains a convergent operation.

Fixes #213868

>From 1a81627ad20ae6b27742e35ac41eb3e61cf904d0 Mon Sep 17 00:00:00 2001
From: 39ali <alimilhim5 at gmail.com>
Date: Tue, 4 Aug 2026 11:06:32 +0300
Subject: [PATCH] [JumpThreading] Don't thread into blocks with convergent
 operations

---
 llvm/lib/Transforms/Scalar/JumpThreading.cpp  | 24 +++++
 .../JumpThreading/thread-into-convergent.ll   | 91 +++++++++++++++++++
 2 files changed, 115 insertions(+)
 create mode 100644 llvm/test/Transforms/JumpThreading/thread-into-convergent.ll

diff --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
index 7a0542aac83fc..1ad312110b3b5 100644
--- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
@@ -2147,6 +2147,14 @@ void JumpThreadingPass::cloneInstructions(ValueToValueMapTy &ValueMapping,
   }
 }
 
+/// Return true if BB contains a convergent operation.
+static bool blockContainsConvergentOp(const BasicBlock *BB) {
+  return llvm::any_of(*BB, [](const Instruction &I) {
+    const auto *CB = dyn_cast<CallBase>(&I);
+    return CB && CB->isConvergent();
+  });
+}
+
 /// Attempt to thread through two successive basic blocks.
 bool JumpThreadingPass::maybethreadThroughTwoBasicBlocks(BasicBlock *BB,
                                                          Value *Cond) {
@@ -2267,6 +2275,14 @@ bool JumpThreadingPass::maybethreadThroughTwoBasicBlocks(BasicBlock *BB,
     return false;
   }
 
+  // Don't create a new entry edge into a block with convergent operations.
+  if (blockContainsConvergentOp(SuccBB)) {
+    LLVM_DEBUG(dbgs() << "  Not threading through BB '" << BB->getName()
+                      << "' to dest BB '" << SuccBB->getName()
+                      << "' - destination contains convergent operations!\n");
+    return false;
+  }
+
   // Compute the cost of duplicating BB and PredBB.
   unsigned BBCost = getJumpThreadDuplicationCost(
       TTI, BB, BB->getTerminator(), BBDupThreshold);
@@ -2389,6 +2405,14 @@ bool JumpThreadingPass::tryThreadEdge(
     return false;
   }
 
+  // Don't create a new entry edge into a block with convergent operations.
+  if (blockContainsConvergentOp(SuccBB)) {
+    LLVM_DEBUG(dbgs() << "  Not threading across BB '" << BB->getName()
+                      << "' to dest BB '" << SuccBB->getName()
+                      << "' - destination contains convergent operations!\n");
+    return false;
+  }
+
   unsigned JumpThreadCost = getJumpThreadDuplicationCost(
       TTI, BB, BB->getTerminator(), BBDupThreshold);
   if (JumpThreadCost > BBDupThreshold) {
diff --git a/llvm/test/Transforms/JumpThreading/thread-into-convergent.ll b/llvm/test/Transforms/JumpThreading/thread-into-convergent.ll
new file mode 100644
index 0000000000000..9d1adc7bb314c
--- /dev/null
+++ b/llvm/test/Transforms/JumpThreading/thread-into-convergent.ll
@@ -0,0 +1,91 @@
+; RUN: opt -S -passes=jump-threading < %s | FileCheck %s
+
+; Jump threading must not create a new entry edge into a block that contains
+; convergent operations: the new edge changes the control-flow paths by which
+; threads reach the convergent call, and targets using the token-less
+; `convergent` attribute  derive warp reconvergence points from
+; the CFG merge structure. Threading past the %retest merge leaves no valid
+; reconvergence point between the divergence introduced in %A/%C and the
+; convergent call, so the call executes with a partially converged warp.
+
+declare void @convergent_op() convergent
+declare void @plain_op()
+
+; %t and %f are opaque (runtime-uniform) conditions. On the %skip path %t is
+; known false, so jump threading wants to duplicate %mid+%retest and route
+; that path directly into %tail — a new entry edge into a convergent block.
+; That must be refused. The %ret_body branch in between keeps the remaining
+; structure from folding away.
+
+define void @dont_thread_into_convergent(i1 %t, i1 %f) {
+; CHECK-LABEL: @dont_thread_into_convergent(
+; CHECK: retest:
+; CHECK: br i1 %t, label %C, label %tail
+; CHECK: tail:
+; CHECK-NOT: tail.thread
+; CHECK: call void @convergent_op()
+entry:
+  br i1 %t, label %A, label %skip
+
+A:
+  call void @plain_op()
+  br label %mid
+
+skip:
+  br label %mid
+
+mid:
+  br i1 %f, label %ret_body, label %retest
+
+ret_body:
+  call void @plain_op()
+  ret void
+
+retest:
+  br i1 %t, label %C, label %tail
+
+C:
+  call void @plain_op()
+  br label %tail
+
+tail:
+  call void @convergent_op()
+  ret void
+}
+
+; Same shape without the convergent call: threading fires and %tail2 gets the
+; threaded entry edge. Guards against the new check being overly broad.
+
+define void @thread_into_plain(i1 %t, i1 %f) {
+; CHECK-LABEL: @thread_into_plain(
+; CHECK: mid2.thread:
+; CHECK: tail2:
+; CHECK-SAME: preds = %mid2.thread
+entry:
+  br i1 %t, label %A2, label %skip2
+
+A2:
+  call void @plain_op()
+  br label %mid2
+
+skip2:
+  br label %mid2
+
+mid2:
+  br i1 %f, label %ret_body2, label %retest2
+
+ret_body2:
+  call void @plain_op()
+  ret void
+
+retest2:
+  br i1 %t, label %C2, label %tail2
+
+C2:
+  call void @plain_op()
+  br label %tail2
+
+tail2:
+  call void @plain_op()
+  ret void
+}



More information about the llvm-commits mailing list