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

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 3 22:08:26 PDT 2026


Author: Shilei Tian
Date: 2026-08-04T05:08:20Z
New Revision: f914343e531e2feb35e29bab1c42e103ab15aaf5

URL: https://github.com/llvm/llvm-project/commit/f914343e531e2feb35e29bab1c42e103ab15aaf5
DIFF: https://github.com/llvm/llvm-project/commit/f914343e531e2feb35e29bab1c42e103ab15aaf5.diff

LOG: [SimplifyCFG] Do not thread branches into uncontrolled convergent regions (#204958)

SimplifyCFG's foldCondBranchOnValueKnownInPredecessor redirects a
predecessor edge around a block whose branch condition is known on that
edge. When the bypassed block and the threading destination are on a
common cycle, this can change the cycle structure and with it the
dynamic instance of an uncontrolled convergent operation inside that
cycle.

On targets with branch divergence, this PR skips the candidate when the
destination can reach an uncontrolled convergent call on a path back to
the bypassed block. Operations using convergence control tokens are left
alone.

Fixes ROCM-26496.

Added: 
    

Modified: 
    llvm/lib/Transforms/Utils/SimplifyCFG.cpp
    llvm/test/Transforms/SimplifyCFG/AMDGPU/convergent-jump-threading.ll

Removed: 
    


################################################################################
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-commits mailing list