[llvm] [NFC][SPIRV] Move `SPIRVStripConvergenceIntrinsics` to Utils (PR #188537)

Finn Plummer via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 15 10:01:14 PDT 2026


https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/188537

>From 49f672e1c1e551ffbe82b131b5fd7d8de174a39d Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 25 Mar 2026 16:29:26 +0000
Subject: [PATCH 1/6] [NFC][SPIRV] Move `SPIRVStripConvergenceIntrinsics` to
 Utils

The `SPIRVStripConvergenceIntrinsic` pass was written as a spirv pass as
it is the currently the only target that emits convergence tokens during
codegen. There is nothing target specific to the pass, and, we plan to
emit convergence tokens when targeting DirectX and we can move the pass
to a common place.

Enables the pass for targeting DirectX and is a pre-req for: TODO.
---
 llvm/include/llvm/InitializePasses.h          |  2 +
 llvm/include/llvm/Transforms/Utils.h          |  7 ++
 .../Utils/StripConvergenceIntrinsics.h        | 29 ++++++
 llvm/lib/Passes/PassBuilder.cpp               |  1 +
 llvm/lib/Passes/PassRegistry.def              |  1 +
 .../Target/DirectX/DirectXTargetMachine.cpp   |  3 +
 llvm/lib/Target/SPIRV/CMakeLists.txt          |  1 -
 llvm/lib/Target/SPIRV/SPIRV.h                 |  2 -
 .../SPIRV/SPIRVStripConvergentIntrinsics.cpp  | 86 ----------------
 llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp  |  3 +-
 llvm/lib/Transforms/Utils/CMakeLists.txt      |  1 +
 .../Utils/StripConvergenceIntrinsics.cpp      | 99 +++++++++++++++++++
 llvm/test/CodeGen/DirectX/llc-pipeline.ll     |  3 +
 .../DirectX/strip-convergence-intrinsics.ll   | 38 +++++++
 llvm/test/CodeGen/SPIRV/llc-pipeline.ll       |  4 +-
 .../StripConvergenceIntrinsics/basic.ll       | 58 +++++++++++
 16 files changed, 245 insertions(+), 93 deletions(-)
 create mode 100644 llvm/include/llvm/Transforms/Utils/StripConvergenceIntrinsics.h
 delete mode 100644 llvm/lib/Target/SPIRV/SPIRVStripConvergentIntrinsics.cpp
 create mode 100644 llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
 create mode 100644 llvm/test/CodeGen/DirectX/strip-convergence-intrinsics.ll
 create mode 100644 llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll

diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h
index 48e4ecd8ee2af..4a566eed16e32 100644
--- a/llvm/include/llvm/InitializePasses.h
+++ b/llvm/include/llvm/InitializePasses.h
@@ -324,6 +324,8 @@ LLVM_ABI void initializeStackSafetyInfoWrapperPassPass(PassRegistry &);
 LLVM_ABI void initializeStackSlotColoringLegacyPass(PassRegistry &);
 LLVM_ABI void
 initializeStraightLineStrengthReduceLegacyPassPass(PassRegistry &);
+LLVM_ABI void
+initializeStripConvergenceIntrinsicsLegacyPassPass(PassRegistry &);
 LLVM_ABI void initializeStripDebugMachineModulePass(PassRegistry &);
 LLVM_ABI void initializeStructurizeCFGLegacyPassPass(PassRegistry &);
 LLVM_ABI void initializeTailCallElimPass(PassRegistry &);
diff --git a/llvm/include/llvm/Transforms/Utils.h b/llvm/include/llvm/Transforms/Utils.h
index 840e085379a1d..c4e4dffc80c66 100644
--- a/llvm/include/llvm/Transforms/Utils.h
+++ b/llvm/include/llvm/Transforms/Utils.h
@@ -129,6 +129,13 @@ LLVM_ABI Pass *createCanonicalizeFreezeInLoopsPass();
 // functions that are registered in @llvm.global_ctors and which contain a call
 // to `__cxa_atexit` to register their destructor functions.
 LLVM_ABI ModulePass *createLowerGlobalDtorsLegacyPass();
+
+//===----------------------------------------------------------------------===//
+//
+// createStripConvergenceIntrinsicsPass - Strip convergence intrinsics and
+// convergencectrl operand bundles.
+//
+LLVM_ABI FunctionPass *createStripConvergenceIntrinsicsPass();
 } // namespace llvm
 
 #endif
diff --git a/llvm/include/llvm/Transforms/Utils/StripConvergenceIntrinsics.h b/llvm/include/llvm/Transforms/Utils/StripConvergenceIntrinsics.h
new file mode 100644
index 0000000000000..96a24641e34d2
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/StripConvergenceIntrinsics.h
@@ -0,0 +1,29 @@
+//===- StripConvergenceIntrinsics.h -----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// This pass strips convergence intrinsics and operand bundles as those are
+/// only useful when modifying the CFG during IR passes.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_STRIPCONVERGENCEINTRINSICS_H
+#define LLVM_TRANSFORMS_UTILS_STRIPCONVERGENCEINTRINSICS_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class StripConvergenceIntrinsicsPass
+    : public PassInfoMixin<StripConvergenceIntrinsicsPass> {
+public:
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &);
+};
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_STRIPCONVERGENCEINTRINSICS_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index a23d64b491a79..379aea8e07c8b 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -374,6 +374,7 @@
 #include "llvm/Transforms/Utils/PredicateInfo.h"
 #include "llvm/Transforms/Utils/ProfileVerify.h"
 #include "llvm/Transforms/Utils/RelLookupTableConverter.h"
+#include "llvm/Transforms/Utils/StripConvergenceIntrinsics.h"
 #include "llvm/Transforms/Utils/StripGCRelocates.h"
 #include "llvm/Transforms/Utils/StripNonLineTableDebugInfo.h"
 #include "llvm/Transforms/Utils/SymbolRewriter.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index c92d93d7ae396..82f73faefb624 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -543,6 +543,7 @@ FUNCTION_PASS("sjlj-eh-prepare", SjLjEHPreparePass(TM))
 FUNCTION_PASS("slp-vectorizer", SLPVectorizerPass())
 FUNCTION_PASS("slsr", StraightLineStrengthReducePass())
 FUNCTION_PASS("stack-protector", StackProtectorPass(*TM))
+FUNCTION_PASS("strip-convergence-intrinsics", StripConvergenceIntrinsicsPass())
 FUNCTION_PASS("strip-gc-relocates", StripGCRelocates())
 FUNCTION_PASS("tailcallelim", TailCallElimPass())
 FUNCTION_PASS("transform-warning", WarnMissedTransformationsPass())
diff --git a/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp b/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
index c0a92f92e1fba..23bd820883b92 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
@@ -50,6 +50,7 @@
 #include "llvm/Transforms/IPO/GlobalDCE.h"
 #include "llvm/Transforms/Scalar.h"
 #include "llvm/Transforms/Scalar/Scalarizer.h"
+#include "llvm/Transforms/Utils.h"
 #include <optional>
 
 using namespace llvm;
@@ -81,6 +82,7 @@ LLVMInitializeDirectXTarget() {
   initializeDXILForwardHandleAccessesLegacyPass(*PR);
   initializeDSELegacyPassPass(*PR);
   initializeDXILCBufferAccessLegacyPass(*PR);
+  initializeStripConvergenceIntrinsicsLegacyPassPass(*PR);
 }
 
 class DXILTargetObjectFile : public TargetLoweringObjectFile {
@@ -125,6 +127,7 @@ class DirectXPassConfig : public TargetPassConfig {
     addPass(createDeadStoreEliminationPass());
     addPass(createDXILLegalizeLegacyPass());
     addPass(createDXILResourceImplicitBindingLegacyPass());
+    addPass(createStripConvergenceIntrinsicsPass());
     addPass(createDXILTranslateMetadataLegacyPass());
     addPass(createDXILPostOptimizationValidationLegacyPass());
     addPass(createDXILOpLoweringLegacyPass());
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 0ce96c23603ff..6c79856c713e9 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -29,7 +29,6 @@ add_llvm_target(SPIRVCodeGen
   SPIRVInstructionSelector.cpp
   SPIRVLegalizeImplicitBinding.cpp
   SPIRVLegalizeZeroSizeArrays.cpp
-  SPIRVStripConvergentIntrinsics.cpp
   SPIRVLegalizePointerCast.cpp
   SPIRVMergeRegionExitTargets.cpp
   SPIRVISelLowering.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 448a1f0ddaf96..231bbdcd5d304 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -24,7 +24,6 @@ FunctionPass *createSPIRVStructurizerPass();
 ModulePass *createSPIRVCBufferAccessLegacyPass();
 ModulePass *createSPIRVPushConstantAccessLegacyPass(SPIRVTargetMachine *TM);
 FunctionPass *createSPIRVMergeRegionExitTargetsPass();
-FunctionPass *createSPIRVStripConvergenceIntrinsicsPass();
 ModulePass *createSPIRVLegalizeImplicitBindingPass();
 ModulePass *createSPIRVLegalizeZeroSizeArraysPass(const SPIRVTargetMachine &TM);
 FunctionPass *createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM);
@@ -57,7 +56,6 @@ void initializeSPIRVRegularizerPass(PassRegistry &);
 void initializeSPIRVMergeRegionExitTargetsPass(PassRegistry &);
 void initializeSPIRVPrepareFunctionsPass(PassRegistry &);
 void initializeSPIRVPrepareGlobalsPass(PassRegistry &);
-void initializeSPIRVStripConvergentIntrinsicsPass(PassRegistry &);
 void initializeSPIRVLegalizeImplicitBindingPass(PassRegistry &);
 void initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PassRegistry &);
 void initializeSPIRVCtorDtorLoweringLegacyPass(PassRegistry &);
diff --git a/llvm/lib/Target/SPIRV/SPIRVStripConvergentIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVStripConvergentIntrinsics.cpp
deleted file mode 100644
index b1a8d1ab8a297..0000000000000
--- a/llvm/lib/Target/SPIRV/SPIRVStripConvergentIntrinsics.cpp
+++ /dev/null
@@ -1,86 +0,0 @@
-//===-- SPIRVStripConvergentIntrinsics.cpp ----------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This pass trims convergence intrinsics as those were only useful when
-// modifying the CFG during IR passes.
-//
-//===----------------------------------------------------------------------===//
-
-#include "SPIRV.h"
-#include "SPIRVSubtarget.h"
-#include "SPIRVUtils.h"
-#include "llvm/IR/IntrinsicInst.h"
-#include "llvm/IR/Intrinsics.h"
-#include "llvm/Transforms/Utils/Cloning.h"
-#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
-
-using namespace llvm;
-
-namespace {
-class SPIRVStripConvergentIntrinsics : public FunctionPass {
-public:
-  static char ID;
-
-  SPIRVStripConvergentIntrinsics() : FunctionPass(ID) {}
-
-  bool runOnFunction(Function &F) override {
-    DenseSet<Instruction *> ToRemove;
-
-    // Is the instruction is a convergent intrinsic, add it to kill-list and
-    // returns true. Returns false otherwise.
-    auto CleanupIntrinsic = [&](IntrinsicInst *II) {
-      if (II->getIntrinsicID() != Intrinsic::experimental_convergence_entry &&
-          II->getIntrinsicID() != Intrinsic::experimental_convergence_loop &&
-          II->getIntrinsicID() != Intrinsic::experimental_convergence_anchor)
-        return false;
-
-      II->replaceAllUsesWith(UndefValue::get(II->getType()));
-      ToRemove.insert(II);
-      return true;
-    };
-
-    // Replace the given CallInst by a similar CallInst with no convergencectrl
-    // attribute.
-    auto CleanupCall = [&](CallInst *CI) {
-      auto OB = CI->getOperandBundle(LLVMContext::OB_convergencectrl);
-      if (!OB.has_value())
-        return;
-
-      auto *NewCall = CallBase::removeOperandBundle(
-          CI, LLVMContext::OB_convergencectrl, CI->getIterator());
-      NewCall->copyMetadata(*CI);
-      CI->replaceAllUsesWith(NewCall);
-      ToRemove.insert(CI);
-    };
-
-    for (BasicBlock &BB : F) {
-      for (Instruction &I : BB) {
-        if (auto *II = dyn_cast<IntrinsicInst>(&I))
-          if (CleanupIntrinsic(II))
-            continue;
-        if (auto *CI = dyn_cast<CallInst>(&I))
-          CleanupCall(CI);
-      }
-    }
-
-    // All usages must be removed before their definition is removed.
-    for (Instruction *I : ToRemove)
-      I->eraseFromParent();
-
-    return ToRemove.size() != 0;
-  }
-};
-} // namespace
-
-char SPIRVStripConvergentIntrinsics::ID = 0;
-INITIALIZE_PASS(SPIRVStripConvergentIntrinsics, "strip-convergent-intrinsics",
-                "SPIRV strip convergent intrinsics", false, false)
-
-FunctionPass *llvm::createSPIRVStripConvergenceIntrinsicsPass() {
-  return new SPIRVStripConvergentIntrinsics();
-}
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 061bfc21bc447..cb3e46a0b5ce0 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -66,7 +66,6 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
   initializeSPIRVEmitNonSemanticDIPass(PR);
   initializeSPIRVPrepareFunctionsPass(PR);
   initializeSPIRVPrepareGlobalsPass(PR);
-  initializeSPIRVStripConvergentIntrinsicsPass(PR);
   initializeSPIRVCtorDtorLoweringLegacyPass(PR);
 }
 
@@ -225,7 +224,7 @@ void SPIRVPassConfig::addISelPrepare() {
     addPass(createPromoteMemoryToRegisterPass());
   }
   SPIRVTargetMachine &TM = getTM<SPIRVTargetMachine>();
-  addPass(createSPIRVStripConvergenceIntrinsicsPass());
+  addPass(createStripConvergenceIntrinsicsPass());
   addPass(createSPIRVLegalizeImplicitBindingPass());
   addPass(createSPIRVLegalizeZeroSizeArraysPass(TM));
   addPass(createSPIRVCBufferAccessLegacyPass());
diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt
index 2b5f5cf344e60..82e9edf674866 100644
--- a/llvm/lib/Transforms/Utils/CMakeLists.txt
+++ b/llvm/lib/Transforms/Utils/CMakeLists.txt
@@ -74,6 +74,7 @@ add_llvm_component_library(LLVMTransformUtils
   RelLookupTableConverter.cpp
   ScalarEvolutionExpander.cpp
   SCCPSolver.cpp
+  StripConvergenceIntrinsics.cpp
   StripGCRelocates.cpp
   SSAUpdater.cpp
   SSAUpdaterBulk.cpp
diff --git a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
new file mode 100644
index 0000000000000..a3a665a530984
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass strips convergence intrinsics and convergencectrl operand bundles,
+// as those are only useful when modifying the CFG during IR passes.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/StripConvergenceIntrinsics.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Intrinsics.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+#include "llvm/Transforms/Utils.h"
+
+using namespace llvm;
+
+static bool stripConvergenceIntrinsics(Function &F) {
+  DenseSet<Instruction *> ToRemove;
+
+  auto CleanupIntrinsic = [&](IntrinsicInst *II) {
+    if (II->getIntrinsicID() != Intrinsic::experimental_convergence_entry &&
+        II->getIntrinsicID() != Intrinsic::experimental_convergence_loop &&
+        II->getIntrinsicID() != Intrinsic::experimental_convergence_anchor)
+      return false;
+
+    II->replaceAllUsesWith(UndefValue::get(II->getType()));
+    ToRemove.insert(II);
+    return true;
+  };
+
+  auto CleanupCall = [&](CallInst *CI) {
+    auto OB = CI->getOperandBundle(LLVMContext::OB_convergencectrl);
+    if (!OB.has_value())
+      return;
+
+    auto *NewCall = CallBase::removeOperandBundle(
+        CI, LLVMContext::OB_convergencectrl, CI->getIterator());
+    NewCall->copyMetadata(*CI);
+    CI->replaceAllUsesWith(NewCall);
+    ToRemove.insert(CI);
+  };
+
+  for (BasicBlock &BB : F) {
+    for (Instruction &I : BB) {
+      if (auto *II = dyn_cast<IntrinsicInst>(&I))
+        if (CleanupIntrinsic(II))
+          continue;
+      if (auto *CI = dyn_cast<CallInst>(&I))
+        CleanupCall(CI);
+    }
+  }
+
+  for (Instruction *I : ToRemove)
+    I->eraseFromParent();
+
+  return ToRemove.size() != 0;
+}
+
+PreservedAnalyses
+StripConvergenceIntrinsicsPass::run(Function &F, FunctionAnalysisManager &) {
+  if (!stripConvergenceIntrinsics(F))
+    return PreservedAnalyses::all();
+  return PreservedAnalyses::none();
+}
+
+namespace {
+class StripConvergenceIntrinsicsLegacyPass : public FunctionPass {
+public:
+  static char ID;
+
+  StripConvergenceIntrinsicsLegacyPass() : FunctionPass(ID) {
+    initializeStripConvergenceIntrinsicsLegacyPassPass(
+        *PassRegistry::getPassRegistry());
+  }
+
+  bool runOnFunction(Function &F) override {
+    return stripConvergenceIntrinsics(F);
+  }
+};
+} // namespace
+
+char StripConvergenceIntrinsicsLegacyPass::ID = 0;
+INITIALIZE_PASS(StripConvergenceIntrinsicsLegacyPass,
+                "strip-convergence-intrinsics",
+                "Strip convergence intrinsics and operand bundles", false,
+                false)
+
+FunctionPass *llvm::createStripConvergenceIntrinsicsPass() {
+  return new StripConvergenceIntrinsicsLegacyPass();
+}
diff --git a/llvm/test/CodeGen/DirectX/llc-pipeline.ll b/llvm/test/CodeGen/DirectX/llc-pipeline.ll
index 586567ceb18ec..04e0194690e73 100644
--- a/llvm/test/CodeGen/DirectX/llc-pipeline.ll
+++ b/llvm/test/CodeGen/DirectX/llc-pipeline.ll
@@ -39,11 +39,14 @@
 ; CHECK-NEXT:     DXIL Legalizer
 ; CHECK-NEXT:   DXIL Resource Binding Analysis
 ; CHECK-NEXT:   DXIL Resource Implicit Binding
+; CHECK-NEXT:   FunctionPass Manager
+; CHECK-NEXT:     Strip convergence intrinsics and operand bundles
 ; CHECK-NEXT:   DXIL Resources Analysis
 ; CHECK-NEXT:   DXIL Module Metadata analysis
 ; CHECK-NEXT:   DXIL Shader Flag Analysis
 ; CHECK-NEXT:   DXIL Root Signature Analysis
 ; CHECK-NEXT:   DXIL Translate Metadata
+; CHECK-NEXT:   DXIL Resource Binding Analysis
 ; CHECK-NEXT:   DXIL Post Optimization Validation
 ; CHECK-NEXT:   DXIL Op Lowering
 ; CHECK-NEXT:   DXIL Prepare Module
diff --git a/llvm/test/CodeGen/DirectX/strip-convergence-intrinsics.ll b/llvm/test/CodeGen/DirectX/strip-convergence-intrinsics.ll
new file mode 100644
index 0000000000000..afd858ad4eec3
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/strip-convergence-intrinsics.ll
@@ -0,0 +1,38 @@
+; RUN: llc %s -mtriple=dxil-pc-shadermodel6.3-library -o - | FileCheck %s
+
+; Verify that convergence intrinsics and operand bundles are stripped
+; during DXIL lowering pipeline.
+
+; CHECK-LABEL: define float @test(
+; CHECK-NOT: convergence
+; CHECK: ret float
+
+; CHECK-LABEL: define void @test_loop(
+; CHECK-NOT: convergence
+; CHECK: ret void
+
+define float @test(float %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  %1 = call float @llvm.dx.dot(float %a) [ "convergencectrl"(token %0) ]
+  ret float %1
+}
+
+define void @test_loop(float %a) convergent {
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  br label %loop
+
+loop:
+  %1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
+  call void @convergent_callee() [ "convergencectrl"(token %1) ]
+  br i1 true, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+declare void @convergent_callee() convergent
+declare float @llvm.dx.dot(float)
+declare token @llvm.experimental.convergence.entry()
+declare token @llvm.experimental.convergence.loop()
diff --git a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
index 3b256f1a3a2dc..81b12caeddec1 100644
--- a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
+++ b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll
@@ -40,7 +40,7 @@
 ; SPIRV-O0-NEXT:    FunctionPass Manager
 ; SPIRV-O0-NEXT:      Lower invoke and unwind, for unwindless code generators
 ; SPIRV-O0-NEXT:      Remove unreachable blocks from the CFG
-; SPIRV-O0-NEXT:      SPIRV strip convergent intrinsics
+; SPIRV-O0-NEXT:      Strip convergence intrinsics and operand bundles
 ; SPIRV-O0-NEXT:    SPIRV Legalize Implicit Binding
 ; SPIRV-O0-NEXT:    SPIRV Legalize Zero-Size Arrays
 ; SPIRV-O0-NEXT:    SPIRV CBuffer Access
@@ -154,7 +154,7 @@
 ; SPIRV-Opt-NEXT:      CodeGen Prepare
 ; SPIRV-Opt-NEXT:      Lower invoke and unwind, for unwindless code generators
 ; SPIRV-Opt-NEXT:      Remove unreachable blocks from the CFG
-; SPIRV-Opt-NEXT:      SPIRV strip convergent intrinsics
+; SPIRV-Opt-NEXT:      Strip convergence intrinsics and operand bundles
 ; SPIRV-Opt-NEXT:    SPIRV Legalize Implicit Binding
 ; SPIRV-Opt-NEXT:    SPIRV Legalize Zero-Size Arrays
 ; SPIRV-Opt-NEXT:    SPIRV CBuffer Access
diff --git a/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll b/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll
new file mode 100644
index 0000000000000..e233075fce2ed
--- /dev/null
+++ b/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll
@@ -0,0 +1,58 @@
+; RUN: opt -passes=strip-convergence-intrinsics -S < %s | FileCheck %s
+
+; Verify that convergence intrinsics and convergencectrl operand bundles are
+; stripped by the pass.
+
+define void @entry_and_call() convergent {
+; CHECK-LABEL: define void @entry_and_call()
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @convergent_callee()
+; CHECK-NOT:     convergence
+; CHECK-NEXT:    ret void
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  call void @convergent_callee() [ "convergencectrl"(token %0) ]
+  ret void
+}
+
+define void @loop_with_token() convergent {
+; CHECK-LABEL: define void @loop_with_token()
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label %loop
+; CHECK:       loop:
+; CHECK-NEXT:    call void @convergent_callee()
+; CHECK-NOT:     convergence
+; CHECK-NEXT:    br i1
+entry:
+  %0 = call token @llvm.experimental.convergence.entry()
+  br label %loop
+
+loop:
+  %1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %0) ]
+  call void @convergent_callee() [ "convergencectrl"(token %1) ]
+  br i1 true, label %exit, label %loop
+
+exit:
+  ret void
+}
+
+define void @anchor_token() convergent {
+; CHECK-LABEL: define void @anchor_token()
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    call void @convergent_callee()
+; CHECK-NOT:     convergence
+; CHECK-NEXT:    ret void
+entry:
+  %0 = call token @llvm.experimental.convergence.anchor()
+  call void @convergent_callee() [ "convergencectrl"(token %0) ]
+  ret void
+}
+
+; Declarations may remain but should have no uses in function bodies.
+; CHECK-LABEL: declare void @convergent_callee
+
+declare void @convergent_callee() convergent
+
+declare token @llvm.experimental.convergence.entry()
+declare token @llvm.experimental.convergence.loop()
+declare token @llvm.experimental.convergence.anchor()

>From ad43112e4529eb5734f3e040979b1d6e67eb154e Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Thu, 26 Mar 2026 16:30:18 +0000
Subject: [PATCH 2/6] nfc: remove use of undef

by going in reverse order we remove the need to create a temporary undef
value
---
 .../Utils/StripConvergenceIntrinsics.cpp      | 66 ++++++++-----------
 1 file changed, 28 insertions(+), 38 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
index a3a665a530984..84ef3e6f48a5d 100644
--- a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
+++ b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
@@ -12,9 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Utils/StripConvergenceIntrinsics.h"
-#include "llvm/ADT/DenseSet.h"
-#include "llvm/IR/IRBuilder.h"
-#include "llvm/IR/InstIterator.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/InitializePasses.h"
@@ -24,45 +22,37 @@
 using namespace llvm;
 
 static bool stripConvergenceIntrinsics(Function &F) {
-  DenseSet<Instruction *> ToRemove;
-
-  auto CleanupIntrinsic = [&](IntrinsicInst *II) {
-    if (II->getIntrinsicID() != Intrinsic::experimental_convergence_entry &&
-        II->getIntrinsicID() != Intrinsic::experimental_convergence_loop &&
-        II->getIntrinsicID() != Intrinsic::experimental_convergence_anchor)
-      return false;
-
-    II->replaceAllUsesWith(UndefValue::get(II->getType()));
-    ToRemove.insert(II);
-    return true;
-  };
-
-  auto CleanupCall = [&](CallInst *CI) {
-    auto OB = CI->getOperandBundle(LLVMContext::OB_convergencectrl);
-    if (!OB.has_value())
-      return;
-
-    auto *NewCall = CallBase::removeOperandBundle(
-        CI, LLVMContext::OB_convergencectrl, CI->getIterator());
-    NewCall->copyMetadata(*CI);
-    CI->replaceAllUsesWith(NewCall);
-    ToRemove.insert(CI);
-  };
-
-  for (BasicBlock &BB : F) {
-    for (Instruction &I : BB) {
-      if (auto *II = dyn_cast<IntrinsicInst>(&I))
-        if (CleanupIntrinsic(II))
+  bool Changed = false;
+
+  // Iterate in reverse order so that uses of convergence tokens are removed
+  // before the convergence intrinsics that define them.
+  for (BasicBlock &BB : reverse(F)) {
+    for (Instruction &I : make_early_inc_range(reverse(BB))) {
+      if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
+        if (II->getIntrinsicID() == Intrinsic::experimental_convergence_entry ||
+            II->getIntrinsicID() == Intrinsic::experimental_convergence_loop ||
+            II->getIntrinsicID() ==
+                Intrinsic::experimental_convergence_anchor) {
+          II->eraseFromParent();
+          Changed = true;
+          continue;
+        }
+      }
+      if (auto *CI = dyn_cast<CallInst>(&I)) {
+        auto OB = CI->getOperandBundle(LLVMContext::OB_convergencectrl);
+        if (!OB.has_value())
           continue;
-      if (auto *CI = dyn_cast<CallInst>(&I))
-        CleanupCall(CI);
+        auto *NewCall = CallBase::removeOperandBundle(
+            CI, LLVMContext::OB_convergencectrl, CI->getIterator());
+        NewCall->copyMetadata(*CI);
+        CI->replaceAllUsesWith(NewCall);
+        CI->eraseFromParent();
+        Changed = true;
+      }
     }
   }
 
-  for (Instruction *I : ToRemove)
-    I->eraseFromParent();
-
-  return ToRemove.size() != 0;
+  return Changed;
 }
 
 PreservedAnalyses

>From 9a79bcbcf1bc058a32ffd7ab070806898c973387 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 14 Apr 2026 23:08:11 +0000
Subject: [PATCH 3/6] review: move pass to happen immediately

---
 llvm/lib/Target/DirectX/DirectXTargetMachine.cpp | 2 +-
 llvm/test/CodeGen/DirectX/llc-pipeline.ll        | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp b/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
index 23bd820883b92..234385828bd3d 100644
--- a/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
+++ b/llvm/lib/Target/DirectX/DirectXTargetMachine.cpp
@@ -112,6 +112,7 @@ class DirectXPassConfig : public TargetPassConfig {
 
   FunctionPass *createTargetRegisterAllocator(bool) override { return nullptr; }
   void addCodeGenPrepare() override {
+    addPass(createStripConvergenceIntrinsicsPass());
     addPass(createDXILFinalizeLinkageLegacyPass());
     addPass(createGlobalDCEPass());
     addPass(createDXILMemIntrinsicsLegacyPass());
@@ -127,7 +128,6 @@ class DirectXPassConfig : public TargetPassConfig {
     addPass(createDeadStoreEliminationPass());
     addPass(createDXILLegalizeLegacyPass());
     addPass(createDXILResourceImplicitBindingLegacyPass());
-    addPass(createStripConvergenceIntrinsicsPass());
     addPass(createDXILTranslateMetadataLegacyPass());
     addPass(createDXILPostOptimizationValidationLegacyPass());
     addPass(createDXILOpLoweringLegacyPass());
diff --git a/llvm/test/CodeGen/DirectX/llc-pipeline.ll b/llvm/test/CodeGen/DirectX/llc-pipeline.ll
index 04e0194690e73..2474e08ac1549 100644
--- a/llvm/test/CodeGen/DirectX/llc-pipeline.ll
+++ b/llvm/test/CodeGen/DirectX/llc-pipeline.ll
@@ -14,6 +14,8 @@
 ; CHECK-OBJ-NEXT: Create Garbage Collector Module Metadata
 
 ; CHECK-NEXT: ModulePass Manager
+; CHECK-NEXT:   FunctionPass Manager
+; CHECK-NEXT:     Strip convergence intrinsics and operand bundles
 ; CHECK-NEXT:   DXIL Finalize Linkage
 ; CHECK-NEXT:   Dead Global Elimination
 ; CHECK-NEXT:   DXIL Memory Intrinsic Elimination
@@ -39,14 +41,11 @@
 ; CHECK-NEXT:     DXIL Legalizer
 ; CHECK-NEXT:   DXIL Resource Binding Analysis
 ; CHECK-NEXT:   DXIL Resource Implicit Binding
-; CHECK-NEXT:   FunctionPass Manager
-; CHECK-NEXT:     Strip convergence intrinsics and operand bundles
 ; CHECK-NEXT:   DXIL Resources Analysis
 ; CHECK-NEXT:   DXIL Module Metadata analysis
 ; CHECK-NEXT:   DXIL Shader Flag Analysis
 ; CHECK-NEXT:   DXIL Root Signature Analysis
 ; CHECK-NEXT:   DXIL Translate Metadata
-; CHECK-NEXT:   DXIL Resource Binding Analysis
 ; CHECK-NEXT:   DXIL Post Optimization Validation
 ; CHECK-NEXT:   DXIL Op Lowering
 ; CHECK-NEXT:   DXIL Prepare Module

>From e2ac9d2b0c59669ecc207a3e156084a905dd6328 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Tue, 14 Apr 2026 23:08:52 +0000
Subject: [PATCH 4/6] review: address use of token before definition of token

---
 .../Utils/StripConvergenceIntrinsics.cpp      | 44 +++++++++++--------
 1 file changed, 26 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
index 84ef3e6f48a5d..b8acca3d39f1e 100644
--- a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
+++ b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
@@ -13,6 +13,7 @@
 
 #include "llvm/Transforms/Utils/StripConvergenceIntrinsics.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
 #include "llvm/InitializePasses.h"
@@ -22,36 +23,43 @@
 using namespace llvm;
 
 static bool stripConvergenceIntrinsics(Function &F) {
+  SmallVector<IntrinsicInst *> ConvergenceIntrinsics;
   bool Changed = false;
 
-  // Iterate in reverse order so that uses of convergence tokens are removed
-  // before the convergence intrinsics that define them.
-  for (BasicBlock &BB : reverse(F)) {
-    for (Instruction &I : make_early_inc_range(reverse(BB))) {
-      if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
-        if (II->getIntrinsicID() == Intrinsic::experimental_convergence_entry ||
-            II->getIntrinsicID() == Intrinsic::experimental_convergence_loop ||
-            II->getIntrinsicID() ==
-                Intrinsic::experimental_convergence_anchor) {
-          II->eraseFromParent();
-          Changed = true;
-          continue;
-        }
-      }
-      if (auto *CI = dyn_cast<CallInst>(&I)) {
-        auto OB = CI->getOperandBundle(LLVMContext::OB_convergencectrl);
-        if (!OB.has_value())
-          continue;
+  for (BasicBlock &BB : F) {
+    for (Instruction &I : make_early_inc_range(BB)) {
+      auto *CI = dyn_cast<CallInst>(&I);
+      if (!CI)
+        continue;
+
+      // Strip a convergencectrl operand bundle if present. Note that
+      // convergence intrinsics (e.g. convergence.loop) may use a
+      // convergencectrl bundle.
+      if (CI->getOperandBundle(LLVMContext::OB_convergencectrl)) {
         auto *NewCall = CallBase::removeOperandBundle(
             CI, LLVMContext::OB_convergencectrl, CI->getIterator());
         NewCall->copyMetadata(*CI);
         CI->replaceAllUsesWith(NewCall);
         CI->eraseFromParent();
+        CI = cast<CallInst>(NewCall);
         Changed = true;
       }
+
+      // Collect convergence intrinsics for deferred removal.
+      if (auto *II = dyn_cast<IntrinsicInst>(CI))
+        if (II->getIntrinsicID() == Intrinsic::experimental_convergence_entry ||
+            II->getIntrinsicID() == Intrinsic::experimental_convergence_loop ||
+            II->getIntrinsicID() == Intrinsic::experimental_convergence_anchor)
+          ConvergenceIntrinsics.push_back(II);
     }
   }
 
+  // Erase all convergence intrinsics now that convergence tokens are no
+  // longer in use.
+  for (IntrinsicInst *II : ConvergenceIntrinsics)
+    II->eraseFromParent();
+  Changed |= !ConvergenceIntrinsics.empty();
+
   return Changed;
 }
 

>From f2ea3454c929bf3c3ae42bd9c87e2331e72ca051 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Apr 2026 16:52:07 +0000
Subject: [PATCH 5/6] review: add demo test case

---
 .../StripConvergenceIntrinsics/basic.ll       | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll b/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll
index e233075fce2ed..62cd4739257ae 100644
--- a/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll
+++ b/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll
@@ -48,6 +48,27 @@ entry:
   ret void
 }
 
+; This ensures that when the use of a token is defined before the call that
+; creates the token, we are still able to strip them as expected.
+define void @reversed_block_order() convergent {
+; CHECK-LABEL: define void @reversed_block_order()
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label %[[B:.*]]
+; CHECK:       [[A:.*]]:
+; CHECK-NEXT:    call void @convergent_callee()
+; CHECK-NEXT:    ret void
+; CHECK:      [[B]]:
+; CHECK-NEXT:    br label %[[A]]
+entry:
+ br label %B
+A:
+ call void @convergent_callee() [ "convergencectrl"(token %tok) ]
+ ret void
+B:
+ %tok = call token @llvm.experimental.convergence.anchor()
+ br label %A
+}
+
 ; Declarations may remain but should have no uses in function bodies.
 ; CHECK-LABEL: declare void @convergent_callee
 

>From 9b0a5eced8d26f7c29e08bb067fee1eb12a406b9 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Apr 2026 16:59:24 +0000
Subject: [PATCH 6/6] review: preserve applicalbe analysses

---
 llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
index b8acca3d39f1e..667e23df667ee 100644
--- a/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
+++ b/llvm/lib/Transforms/Utils/StripConvergenceIntrinsics.cpp
@@ -67,7 +67,9 @@ PreservedAnalyses
 StripConvergenceIntrinsicsPass::run(Function &F, FunctionAnalysisManager &) {
   if (!stripConvergenceIntrinsics(F))
     return PreservedAnalyses::all();
-  return PreservedAnalyses::none();
+  PreservedAnalyses PA;
+  PA.preserveSet<CFGAnalyses>();
+  return PA;
 }
 
 namespace {



More information about the llvm-commits mailing list