diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h index 604118cee9e2e..012f1fb0ebd90 100644 --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -322,6 +322,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 { +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 2751961e684d2..8c9f5a0c395cd 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 9f7b3cf4df77b..2e9d9f3849d17 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -542,6 +542,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..234385828bd3d 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 using namespace llvm; @@ -81,6 +82,7 @@ LLVMInitializeDirectXTarget() { initializeDXILForwardHandleAccessesLegacyPass(*PR); initializeDSELegacyPassPass(*PR); initializeDXILCBufferAccessLegacyPass(*PR); + initializeStripConvergenceIntrinsicsLegacyPassPass(*PR); } class DXILTargetObjectFile : public TargetLoweringObjectFile { @@ -110,6 +112,7 @@ class DirectXPassConfig : public TargetPassConfig { FunctionPass *createTargetRegisterAllocator(bool) override { return nullptr; } void addCodeGenPrepare() override { + addPass(createStripConvergenceIntrinsicsPass()); addPass(createDXILFinalizeLinkageLegacyPass()); addPass(createGlobalDCEPass()); addPass(createDXILMemIntrinsicsLegacyPass()); 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 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(&I)) - if (CleanupIntrinsic(II)) - continue; - if (auto *CI = dyn_cast(&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 6797722579137..24759b1e77d5f 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); } @@ -229,7 +228,7 @@ void SPIRVPassConfig::addISelPrepare() { addPass(createLoopSimplifyPass()); } SPIRVTargetMachine &TM = getTM(); - 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..667e23df667ee --- /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/STLExtras.h" +#include "llvm/ADT/SmallVector.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) { + SmallVector ConvergenceIntrinsics; + bool Changed = false; + + for (BasicBlock &BB : F) { + for (Instruction &I : make_early_inc_range(BB)) { + auto *CI = dyn_cast(&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(NewCall); + Changed = true; + } + + // Collect convergence intrinsics for deferred removal. + if (auto *II = dyn_cast(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; +} + +PreservedAnalyses +StripConvergenceIntrinsicsPass::run(Function &F, FunctionAnalysisManager &) { + if (!stripConvergenceIntrinsics(F)) + return PreservedAnalyses::all(); + PreservedAnalyses PA; + PA.preserveSet(); + return PA; +} + +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..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 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 2e83b3fe07d8b..6111bc22e8ed6 100644 --- a/llvm/test/CodeGen/SPIRV/llc-pipeline.ll +++ b/llvm/test/CodeGen/SPIRV/llc-pipeline.ll @@ -41,7 +41,7 @@ ; SPIRV-O0-NEXT: Dominator Tree Construction ; SPIRV-O0-NEXT: Natural Loop Information ; SPIRV-O0-NEXT: Canonicalize natural loops -; 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 @@ -156,7 +156,7 @@ ; SPIRV-Opt-NEXT: Dominator Tree Construction ; SPIRV-Opt-NEXT: Natural Loop Information ; SPIRV-Opt-NEXT: Canonicalize natural loops -; 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..62cd4739257ae --- /dev/null +++ b/llvm/test/Transforms/StripConvergenceIntrinsics/basic.ll @@ -0,0 +1,79 @@ +; 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 +} + +; 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 + +declare void @convergent_callee() convergent + +declare token @llvm.experimental.convergence.entry() +declare token @llvm.experimental.convergence.loop() +declare token @llvm.experimental.convergence.anchor()