[llvm-branch-commits] [llvm] [SimplifyCFG] Do not thread branches into uncontrolled convergent regions (PR #204958)

Shilei Tian via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Aug 3 16:58:21 PDT 2026


https://github.com/shiltian updated https://github.com/llvm/llvm-project/pull/204958

>From f67bc3bdec20c0c6e58eb1e8c5e3db95b9a94f21 Mon Sep 17 00:00:00 2001
From: Shilei Tian <i at tianshilei.me>
Date: Sat, 20 Jun 2026 00:24:23 -0400
Subject: [PATCH] [SimplifyCFG] Do not thread branches into uncontrolled
 convergent regions

SimplifyCFG's foldCondBranchOnValueKnownInPredecessor can thread an edge past
a block that acts as a reconvergence point. If the threaded destination reaches
an uncontrolled convergent operation before returning to the threaded-through
block, the transform can change which dynamic instance of the convergent
operation is executed.

Add a conservative destination scan for this fold and skip the threading
candidate when it can reach an uncontrolled convergent call before returning
to the original block. Controlled convergent operations using convergence
control tokens are left alone.

Fixes ROCM-26496.
---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp     | 84 +++++++++++++++++--
 .../AMDGPU/convergent-jump-threading.ll       | 55 +++++++++++-
 2 files changed, 130 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 33b08a648d45f..089892dc573f3 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -3545,13 +3545,77 @@ static ConstantInt *getKnownValueOnEdge(Value *V, BasicBlock *From,
   return nullptr;
 }
 
+static bool isUncontrolledConvergentCall(CallBase *CB) {
+  return CB->isConvergent() && !isa<ConvergenceControlInst>(CB) &&
+         !CB->getConvergenceControlToken();
+}
+
+static bool reachesUncontrolledConvergentCallBeforeBlock(BasicBlock *From,
+                                                         BasicBlock *StopBB) {
+  static constexpr unsigned MaxInstructionsToScan = 512;
+
+  // Walk predecessors of StopBB to find blocks that can reach it. Only
+  // convergent calls on a cycle with StopBB matter - a convergent call on a
+  // path to function exit cannot have its dynamic instance changed by
+  // threading.
+  SmallPtrSet<BasicBlock *, 8> CanReachStop;
+  SmallPtrSet<BasicBlock *, 8> BlocksWithUncontrolledConvergentCalls;
+  SmallVector<BasicBlock *, 8> Worklist;
+  for (BasicBlock *Pred : predecessors(StopBB))
+    Worklist.push_back(Pred);
+
+  // Cache blocks with relevant calls while building CanReachStop. This keeps
+  // the instruction scan bounded without a separate block limit.
+  unsigned NumScannedInstructions = 0;
+  while (!Worklist.empty()) {
+    BasicBlock *BB = Worklist.pop_back_val();
+    if (BB == StopBB)
+      continue;
+    if (!CanReachStop.insert(BB).second)
+      continue;
+
+    for (Instruction &I : *BB) {
+      if (++NumScannedInstructions > MaxInstructionsToScan)
+        return true;
+      auto *CB = dyn_cast<CallBase>(&I);
+      if (CB && isUncontrolledConvergentCall(CB)) {
+        BlocksWithUncontrolledConvergentCalls.insert(BB);
+        break;
+      }
+    }
+
+    append_range(Worklist, predecessors(BB));
+  }
+
+  if (!CanReachStop.contains(From))
+    return false;
+
+  SmallPtrSet<BasicBlock *, 8> Visited;
+  Worklist.push_back(From);
+
+  while (!Worklist.empty()) {
+    BasicBlock *BB = Worklist.pop_back_val();
+    if (BB == StopBB || !CanReachStop.contains(BB))
+      continue;
+
+    if (!Visited.insert(BB).second)
+      continue;
+
+    if (BlocksWithUncontrolledConvergentCalls.contains(BB))
+      return true;
+
+    append_range(Worklist, successors(BB));
+  }
+
+  return false;
+}
+
 /// If we have a conditional branch on something for which we know the constant
 /// value in predecessors (e.g. a phi node in the current block), thread edges
 /// from the predecessor to their ultimate destination.
-static std::optional<bool>
-foldCondBranchOnValueKnownInPredecessorImpl(CondBrInst *BI, DomTreeUpdater *DTU,
-                                            const DataLayout &DL,
-                                            AssumptionCache *AC) {
+static std::optional<bool> foldCondBranchOnValueKnownInPredecessorImpl(
+    CondBrInst *BI, const TargetTransformInfo &TTI, DomTreeUpdater *DTU,
+    AssumptionCache *AC, const DataLayout &DL) {
   SmallMapVector<ConstantInt *, SmallSetVector<BasicBlock *, 2>, 2> KnownValues;
   BasicBlock *BB = BI->getParent();
   Value *Cond = BI->getCondition();
@@ -3621,6 +3685,14 @@ foldCondBranchOnValueKnownInPredecessorImpl(CondBrInst *BI, DomTreeUpdater *DTU,
     if (ReachesNonLocalUseBlocks.contains(RealDest))
       continue;
 
+    // Threading through a branch can bypass a reconvergence point. If the
+    // destination can execute an uncontrolled convergent operation before
+    // returning to this block, this may change the dynamic instance of that
+    // operation.
+    if (TTI.hasBranchDivergence(BB->getParent()) &&
+        reachesUncontrolledConvergentCallBeforeBlock(RealDest, BB))
+      continue;
+
     LLVM_DEBUG({
       dbgs() << "Condition " << *Cond << " in " << BB->getName()
              << " has value " << *Pair.first << " in predecessors:\n";
@@ -3742,8 +3814,8 @@ bool SimplifyCFGOpt::foldCondBranchOnValueKnownInPredecessor(CondBrInst *BI) {
   bool EverChanged = false;
   do {
     // Note that None means "we changed things, but recurse further."
-    Result =
-        foldCondBranchOnValueKnownInPredecessorImpl(BI, DTU, DL, Options.AC);
+    Result = foldCondBranchOnValueKnownInPredecessorImpl(BI, TTI, DTU,
+                                                         Options.AC, DL);
     EverChanged |= Result == std::nullopt || *Result;
   } while (Result == std::nullopt);
   return EverChanged;
diff --git a/llvm/test/Transforms/SimplifyCFG/AMDGPU/convergent-jump-threading.ll b/llvm/test/Transforms/SimplifyCFG/AMDGPU/convergent-jump-threading.ll
index 64e071981baf5..88cf0d1d7d86e 100644
--- a/llvm/test/Transforms/SimplifyCFG/AMDGPU/convergent-jump-threading.ll
+++ b/llvm/test/Transforms/SimplifyCFG/AMDGPU/convergent-jump-threading.ll
@@ -9,16 +9,18 @@ define void @preserve_loop_header_branch(i1 %cond, ptr %ptr) convergent {
 ; CHECK-SAME: i1 [[COND:%.*]], ptr [[PTR:%.*]]) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
 ; CHECK-NEXT:    call void @barrier() #[[ATTR0]]
-; CHECK-NEXT:    br i1 [[COND]], label %[[PRE_THEN:.*]], label %[[LOOP_LATCH:.*]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[PRE_THEN:.*]], label %[[LOOP_HEADER:.*]]
 ; CHECK:       [[PRE_THEN]]:
 ; CHECK-NEXT:    store i32 1, ptr [[PTR]], align 4
-; CHECK-NEXT:    br label %[[LOOP_BODY:.*]]
+; CHECK-NEXT:    br label %[[LOOP_HEADER]]
+; CHECK:       [[LOOP_HEADER]]:
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_BODY:.*]], label %[[LOOP_LATCH:.*]]
 ; CHECK:       [[LOOP_BODY]]:
 ; CHECK-NEXT:    store i32 2, ptr [[PTR]], align 4
 ; CHECK-NEXT:    br label %[[LOOP_LATCH]]
 ; CHECK:       [[LOOP_LATCH]]:
 ; CHECK-NEXT:    call void @barrier() #[[ATTR0]]
-; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_BODY]], label %[[EXIT:.*]]
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_HEADER]], label %[[EXIT:.*]]
 ; CHECK:       [[EXIT]]:
 ; CHECK-NEXT:    ret void
 ;
@@ -92,3 +94,50 @@ loop.latch:
 exit:
   ret void
 }
+
+define void @thread_convergent_only_on_exit_path(i1 %cond, ptr %ptr) convergent {
+; CHECK-LABEL: define void @thread_convergent_only_on_exit_path(
+; CHECK-SAME: i1 [[COND:%.*]], ptr [[PTR:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void @plain()
+; CHECK-NEXT:    br i1 [[COND]], label %[[PRE_THEN:.*]], label %[[EXIT_CRITEDGE:.*]]
+; CHECK:       [[PRE_THEN]]:
+; CHECK-NEXT:    store i32 1, ptr [[PTR]], align 4
+; CHECK-NEXT:    br label %[[LOOP_BODY:.*]]
+; CHECK:       [[LOOP_BODY]]:
+; CHECK-NEXT:    store i32 2, ptr [[PTR]], align 4
+; CHECK-NEXT:    call void @plain()
+; CHECK-NEXT:    br i1 [[COND]], label %[[LOOP_BODY]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT_CRITEDGE]]:
+; CHECK-NEXT:    call void @plain()
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    call void @barrier() #[[ATTR0]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %pre
+
+pre:
+  call void @plain()
+  br i1 %cond, label %pre.then, label %loop.header
+
+pre.then:
+  store i32 1, ptr %ptr, align 4
+  br label %loop.header
+
+loop.header:
+  br i1 %cond, label %loop.body, label %loop.latch
+
+loop.body:
+  store i32 2, ptr %ptr, align 4
+  br label %loop.latch
+
+loop.latch:
+  call void @plain()
+  br i1 %cond, label %loop.header, label %exit
+
+exit:
+  call void @barrier() convergent
+  ret void
+}



More information about the llvm-branch-commits mailing list