[llvm] [SPIR-V][NewPM] Register IR-level passes with the new pass manager (PR #193660)

Arseniy Obolenskiy via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 22 21:22:08 PDT 2026


https://github.com/aobolensk created https://github.com/llvm/llvm-project/pull/193660

Add NPM wrappers for SPIRVRegularizer, SPIRVLegalizeImplicitBinding, SPIRVLegalizePointerCast, and SPIRVMergeRegionExitTargets

>From fee55b9ccacc415945565eff7f254c3f01fea466 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Thu, 23 Apr 2026 06:20:35 +0200
Subject: [PATCH] [SPIR-V][NewPM] Register IR-level passes with the new pass
 manager

Add NPM wrappers for SPIRVRegularizer, SPIRVLegalizeImplicitBinding, SPIRVLegalizePointerCast, and SPIRVMergeRegionExitTargets
---
 llvm/lib/Target/SPIRV/SPIRV.h                 |   8 +-
 .../SPIRV/SPIRVLegalizeImplicitBinding.cpp    |  50 ++-
 .../SPIRV/SPIRVLegalizeImplicitBinding.h      |  24 ++
 .../Target/SPIRV/SPIRVLegalizePointerCast.cpp |  34 +-
 .../Target/SPIRV/SPIRVLegalizePointerCast.h   |  29 ++
 .../SPIRV/SPIRVMergeRegionExitTargets.cpp     | 322 +++++++++---------
 .../SPIRV/SPIRVMergeRegionExitTargets.h       |  24 ++
 llvm/lib/Target/SPIRV/SPIRVPassRegistry.def   |  11 +
 llvm/lib/Target/SPIRV/SPIRVRegularizer.cpp    |  30 +-
 llvm/lib/Target/SPIRV/SPIRVRegularizer.h      |  23 ++
 llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp  |  13 +-
 .../CodeGen/SPIRV/ctor-dtor-lowering-ir.ll    |   1 +
 .../passes/SPIRVLegalizeImplicitBinding.ll    |  20 ++
 .../SPIRV/passes/SPIRVLegalizePointerCast.ll  |  30 ++
 .../passes/SPIRVMergeRegionExitTargets.ll     |  46 +++
 .../SPIRV/passes/SPIRVRegularizer-i1-icmp.ll  |  24 ++
 16 files changed, 491 insertions(+), 198 deletions(-)
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.h
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.h
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.h
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVRegularizer.h
 create mode 100644 llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/passes/SPIRVMergeRegionExitTargets.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/passes/SPIRVRegularizer-i1-icmp.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 71c200a0d09c3..efc4da613c456 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -50,12 +50,12 @@ void initializeSPIRVStructurizerPass(PassRegistry &);
 void initializeSPIRVCBufferAccessLegacyPass(PassRegistry &);
 void initializeSPIRVPushConstantAccessLegacyPass(PassRegistry &);
 void initializeSPIRVEmitIntrinsicsPass(PassRegistry &);
-void initializeSPIRVLegalizePointerCastPass(PassRegistry &);
-void initializeSPIRVRegularizerPass(PassRegistry &);
-void initializeSPIRVMergeRegionExitTargetsPass(PassRegistry &);
+void initializeSPIRVLegalizePointerCastLegacyPass(PassRegistry &);
+void initializeSPIRVRegularizerLegacyPass(PassRegistry &);
+void initializeSPIRVMergeRegionExitTargetsLegacyPass(PassRegistry &);
 void initializeSPIRVPrepareFunctionsPass(PassRegistry &);
 void initializeSPIRVPrepareGlobalsPass(PassRegistry &);
-void initializeSPIRVLegalizeImplicitBindingPass(PassRegistry &);
+void initializeSPIRVLegalizeImplicitBindingLegacyPass(PassRegistry &);
 void initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PassRegistry &);
 void initializeSPIRVCtorDtorLoweringLegacyPass(PassRegistry &);
 } // namespace llvm
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
index f7be2a1bef7b5..c1ec46255d6b4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
@@ -13,6 +13,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "SPIRVLegalizeImplicitBinding.h"
 #include "SPIRV.h"
 #include "llvm/ADT/BitVector.h"
 #include "llvm/ADT/SmallVector.h"
@@ -28,14 +29,9 @@
 using namespace llvm;
 
 namespace {
-class SPIRVLegalizeImplicitBinding : public ModulePass {
+class SPIRVLegalizeImplicitBindingImpl {
 public:
-  static char ID;
-  SPIRVLegalizeImplicitBinding() : ModulePass(ID) {}
-  StringRef getPassName() const override {
-    return "SPIRV Legalize Implicit Binding";
-  }
-  bool runOnModule(Module &M) override;
+  bool runOnModule(Module &M);
 
 private:
   void collectBindingInfo(Module &M);
@@ -53,6 +49,18 @@ class SPIRVLegalizeImplicitBinding : public ModulePass {
   SmallVector<CallInst *, 16> ImplicitBindingCalls;
 };
 
+class SPIRVLegalizeImplicitBindingLegacy : public ModulePass {
+public:
+  static char ID;
+  SPIRVLegalizeImplicitBindingLegacy() : ModulePass(ID) {}
+  StringRef getPassName() const override {
+    return "SPIRV Legalize Implicit Binding";
+  }
+  bool runOnModule(Module &M) override {
+    return SPIRVLegalizeImplicitBindingImpl().runOnModule(M);
+  }
+};
+
 struct BindingInfoCollector : public InstVisitor<BindingInfoCollector> {
   std::vector<BitVector> &UsedBindings;
   SmallVector<CallInst *, 16> &ImplicitBindingCalls;
@@ -129,7 +137,7 @@ static uint32_t getDescSet(const CallInst *CI) {
   return cast<ConstantInt>(CI->getArgOperand(DescSetArgIdx))->getZExtValue();
 }
 
-void SPIRVLegalizeImplicitBinding::collectBindingInfo(Module &M) {
+void SPIRVLegalizeImplicitBindingImpl::collectBindingInfo(Module &M) {
   BindingInfoCollector InfoCollector(UsedBindings, ImplicitBindingCalls);
   InfoCollector.visit(M);
 
@@ -140,7 +148,7 @@ void SPIRVLegalizeImplicitBinding::collectBindingInfo(Module &M) {
             });
 }
 
-void SPIRVLegalizeImplicitBinding::verifyUniqueOrderIdPerResource(
+void SPIRVLegalizeImplicitBindingImpl::verifyUniqueOrderIdPerResource(
     SmallVectorImpl<CallInst *> &Calls) {
   // Check that the order Id is unique per resource.
   for (uint32_t i = 1; i < Calls.size(); ++i) {
@@ -157,7 +165,7 @@ void SPIRVLegalizeImplicitBinding::verifyUniqueOrderIdPerResource(
   }
 }
 
-uint32_t SPIRVLegalizeImplicitBinding::getAndReserveFirstUnusedBinding(
+uint32_t SPIRVLegalizeImplicitBindingImpl::getAndReserveFirstUnusedBinding(
     uint32_t DescSet) {
   if (UsedBindings.size() <= DescSet) {
     UsedBindings.resize(DescSet + 1);
@@ -174,7 +182,7 @@ uint32_t SPIRVLegalizeImplicitBinding::getAndReserveFirstUnusedBinding(
   return NewBinding;
 }
 
-void SPIRVLegalizeImplicitBinding::replaceImplicitBindingCalls(Module &M) {
+void SPIRVLegalizeImplicitBindingImpl::replaceImplicitBindingCalls(Module &M) {
   uint32_t lastOrderId = -1;
   uint32_t lastBindingNumber = -1;
 
@@ -202,7 +210,7 @@ void SPIRVLegalizeImplicitBinding::replaceImplicitBindingCalls(Module &M) {
   }
 }
 
-bool SPIRVLegalizeImplicitBinding::runOnModule(Module &M) {
+bool SPIRVLegalizeImplicitBindingImpl::runOnModule(Module &M) {
   collectBindingInfo(M);
   if (ImplicitBindingCalls.empty()) {
     return false;
@@ -214,16 +222,24 @@ bool SPIRVLegalizeImplicitBinding::runOnModule(Module &M) {
 }
 } // namespace
 
-char SPIRVLegalizeImplicitBinding::ID = 0;
+PreservedAnalyses
+SPIRVLegalizeImplicitBinding::run(Module &M, ModuleAnalysisManager &AM) {
+  return SPIRVLegalizeImplicitBindingImpl().runOnModule(M)
+             ? PreservedAnalyses::none()
+             : PreservedAnalyses::all();
+}
+
+char SPIRVLegalizeImplicitBindingLegacy::ID = 0;
 
-INITIALIZE_PASS(SPIRVLegalizeImplicitBinding, "legalize-spirv-implicit-binding",
+INITIALIZE_PASS(SPIRVLegalizeImplicitBindingLegacy,
+                "legalize-spirv-implicit-binding",
                 "Legalize SPIR-V implicit bindings", false, false)
 
 ModulePass *llvm::createSPIRVLegalizeImplicitBindingPass() {
-  return new SPIRVLegalizeImplicitBinding();
+  return new SPIRVLegalizeImplicitBindingLegacy();
 }
 
-void SPIRVLegalizeImplicitBinding::replaceResourceHandleCall(
+void SPIRVLegalizeImplicitBindingImpl::replaceResourceHandleCall(
     Module &M, CallInst *OldCI, uint32_t NewBinding) {
   IRBuilder<> Builder(OldCI);
   const uint32_t DescSet =
@@ -247,7 +263,7 @@ void SPIRVLegalizeImplicitBinding::replaceResourceHandleCall(
   OldCI->eraseFromParent();
 }
 
-void SPIRVLegalizeImplicitBinding::replaceCounterHandleCall(
+void SPIRVLegalizeImplicitBindingImpl::replaceCounterHandleCall(
     Module &M, CallInst *OldCI, uint32_t NewBinding) {
   IRBuilder<> Builder(OldCI);
   const uint32_t DescSet =
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.h b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.h
new file mode 100644
index 0000000000000..9c43690bae6d8
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.h
@@ -0,0 +1,24 @@
+//===- SPIRVLegalizeImplicitBinding.h - Legalize implicit bindings -*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEIMPLICITBINDING_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEIMPLICITBINDING_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class SPIRVLegalizeImplicitBinding
+    : public PassInfoMixin<SPIRVLegalizeImplicitBinding> {
+public:
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEIMPLICITBINDING_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
index 86a54e1274b9a..b4e0c478c7900 100644
--- a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.cpp
@@ -42,6 +42,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "SPIRVLegalizePointerCast.h"
 #include "SPIRV.h"
 #include "SPIRVSubtarget.h"
 #include "SPIRVTargetMachine.h"
@@ -56,7 +57,7 @@
 using namespace llvm;
 
 namespace {
-class SPIRVLegalizePointerCast : public FunctionPass {
+class SPIRVLegalizePointerCastImpl {
 
   // Builds the `spv_assign_type` assigning |Ty| to |Value| at the current
   // builder position.
@@ -531,10 +532,10 @@ class SPIRVLegalizePointerCast : public FunctionPass {
   }
 
 public:
-  SPIRVLegalizePointerCast(SPIRVTargetMachine *TM) : FunctionPass(ID), TM(TM) {}
+  SPIRVLegalizePointerCastImpl(const SPIRVTargetMachine &TM) : TM(TM) {}
 
-  bool runOnFunction(Function &F) override {
-    const SPIRVSubtarget &ST = TM->getSubtarget<SPIRVSubtarget>(F);
+  bool run(Function &F) {
+    const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(F);
     GR = ST.getSPIRVGlobalRegistry();
     DeadInstructions.clear();
 
@@ -557,19 +558,36 @@ class SPIRVLegalizePointerCast : public FunctionPass {
   }
 
 private:
-  SPIRVTargetMachine *TM = nullptr;
+  const SPIRVTargetMachine &TM;
   SPIRVGlobalRegistry *GR = nullptr;
   std::vector<Instruction *> DeadInstructions;
+};
 
+class SPIRVLegalizePointerCastLegacy : public FunctionPass {
 public:
   static char ID;
+  SPIRVLegalizePointerCastLegacy(SPIRVTargetMachine *TM)
+      : FunctionPass(ID), TM(TM) {}
+
+  bool runOnFunction(Function &F) override {
+    return SPIRVLegalizePointerCastImpl(*TM).run(F);
+  }
+
+private:
+  SPIRVTargetMachine *TM = nullptr;
 };
 } // namespace
 
-char SPIRVLegalizePointerCast::ID = 0;
-INITIALIZE_PASS(SPIRVLegalizePointerCast, "spirv-legalize-bitcast",
+PreservedAnalyses
+SPIRVLegalizePointerCast::run(Function &F, FunctionAnalysisManager &AM) {
+  return SPIRVLegalizePointerCastImpl(TM).run(F) ? PreservedAnalyses::none()
+                                                 : PreservedAnalyses::all();
+}
+
+char SPIRVLegalizePointerCastLegacy::ID = 0;
+INITIALIZE_PASS(SPIRVLegalizePointerCastLegacy, "spirv-legalize-bitcast",
                 "SPIRV legalize bitcast pass", false, false)
 
 FunctionPass *llvm::createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM) {
-  return new SPIRVLegalizePointerCast(TM);
+  return new SPIRVLegalizePointerCastLegacy(TM);
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.h b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.h
new file mode 100644
index 0000000000000..438415cb3bc8f
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizePointerCast.h
@@ -0,0 +1,29 @@
+//===-- SPIRVLegalizePointerCast.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEPOINTERCAST_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEPOINTERCAST_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class SPIRVTargetMachine;
+
+class SPIRVLegalizePointerCast
+    : public PassInfoMixin<SPIRVLegalizePointerCast> {
+  const SPIRVTargetMachine &TM;
+
+public:
+  explicit SPIRVLegalizePointerCast(const SPIRVTargetMachine &TM) : TM(TM) {}
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEPOINTERCAST_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.cpp b/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.cpp
index 54ead93f2d52d..c6112554f794a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.cpp
@@ -12,6 +12,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "SPIRVMergeRegionExitTargets.h"
 #include "Analysis/SPIRVConvergenceRegionAnalysis.h"
 #include "SPIRV.h"
 #include "SPIRVSubtarget.h"
@@ -31,178 +32,183 @@ using namespace llvm;
 
 namespace {
 
-class SPIRVMergeRegionExitTargets : public FunctionPass {
-public:
-  static char ID;
-
-  SPIRVMergeRegionExitTargets() : FunctionPass(ID) {}
-
-  /// Create a value in BB set to the value associated with the branch the block
-  /// terminator will take.
-  llvm::Value *createExitVariable(
-      BasicBlock *BB,
-      const DenseMap<BasicBlock *, ConstantInt *> &TargetToValue) {
-    auto *T = BB->getTerminator();
-    if (isa<ReturnInst>(T))
-      return nullptr;
-    if (auto *BI = dyn_cast<UncondBrInst>(T))
-      return TargetToValue.lookup(BI->getSuccessor());
-
-    IRBuilder<> Builder(BB);
-    Builder.SetInsertPoint(T);
-
-    if (auto *BI = dyn_cast<CondBrInst>(T)) {
-      Value *LHS = TargetToValue.lookup(BI->getSuccessor(0));
-      Value *RHS = TargetToValue.lookup(BI->getSuccessor(1));
-
-      if (LHS == nullptr || RHS == nullptr)
-        return LHS == nullptr ? RHS : LHS;
-      return Builder.CreateSelect(BI->getCondition(), LHS, RHS);
-    }
-
-    // TODO: add support for switch cases.
-    llvm_unreachable("Unhandled terminator type.");
+/// Create a value in BB set to the value associated with the branch the block
+/// terminator will take.
+static llvm::Value *
+createExitVariable(BasicBlock *BB,
+                   const DenseMap<BasicBlock *, ConstantInt *> &TargetToValue) {
+  auto *T = BB->getTerminator();
+  if (isa<ReturnInst>(T))
+    return nullptr;
+  if (auto *BI = dyn_cast<UncondBrInst>(T))
+    return TargetToValue.lookup(BI->getSuccessor());
+
+  IRBuilder<> Builder(BB);
+  Builder.SetInsertPoint(T);
+
+  if (auto *BI = dyn_cast<CondBrInst>(T)) {
+    Value *LHS = TargetToValue.lookup(BI->getSuccessor(0));
+    Value *RHS = TargetToValue.lookup(BI->getSuccessor(1));
+
+    if (LHS == nullptr || RHS == nullptr)
+      return LHS == nullptr ? RHS : LHS;
+    return Builder.CreateSelect(BI->getCondition(), LHS, RHS);
   }
 
-  AllocaInst *CreateVariable(Function &F, Type *Type,
-                             BasicBlock::iterator Position) {
-    const DataLayout &DL = F.getDataLayout();
-    return new AllocaInst(Type, DL.getAllocaAddrSpace(), nullptr, "reg",
-                          Position);
-  }
+  // TODO: add support for switch cases.
+  llvm_unreachable("Unhandled terminator type.");
+}
 
-  // Run the pass on the given convergence region, ignoring the sub-regions.
-  // Returns true if the CFG changed, false otherwise.
-  bool runOnConvergenceRegionNoRecurse(LoopInfo &LI,
-                                       SPIRV::ConvergenceRegion *CR) {
-    // Gather all the exit targets for this region.
-    SmallPtrSet<BasicBlock *, 4> ExitTargets;
-    for (BasicBlock *Exit : CR->Exits) {
-      for (BasicBlock *Target : successors(Exit)) {
-        if (CR->Blocks.count(Target) == 0)
-          ExitTargets.insert(Target);
-      }
-    }
+static AllocaInst *createVariable(Function &F, Type *Type,
+                                  BasicBlock::iterator Position) {
+  const DataLayout &DL = F.getDataLayout();
+  return new AllocaInst(Type, DL.getAllocaAddrSpace(), nullptr, "reg",
+                        Position);
+}
 
-    // If we have zero or one exit target, nothing do to.
-    if (ExitTargets.size() <= 1)
-      return false;
-
-    // Create the new single exit target.
-    auto F = CR->Entry->getParent();
-    auto NewExitTarget = BasicBlock::Create(F->getContext(), "new.exit", F);
-    IRBuilder<> Builder(NewExitTarget);
-
-    AllocaInst *Variable = CreateVariable(*F, Builder.getInt32Ty(),
-                                          F->begin()->getFirstInsertionPt());
-
-    // CodeGen output needs to be stable. Using the set as-is would order
-    // the targets differently depending on the allocation pattern.
-    // Sorting per basic-block ordering in the function.
-    std::vector<BasicBlock *> SortedExitTargets;
-    std::vector<BasicBlock *> SortedExits;
-    for (BasicBlock &BB : *F) {
-      if (ExitTargets.count(&BB) != 0)
-        SortedExitTargets.push_back(&BB);
-      if (CR->Exits.count(&BB) != 0)
-        SortedExits.push_back(&BB);
+// Run the pass on the given convergence region, ignoring the sub-regions.
+// Returns true if the CFG changed, false otherwise.
+static bool runOnConvergenceRegionNoRecurse(LoopInfo &LI,
+                                            SPIRV::ConvergenceRegion *CR) {
+  // Gather all the exit targets for this region.
+  SmallPtrSet<BasicBlock *, 4> ExitTargets;
+  for (BasicBlock *Exit : CR->Exits) {
+    for (BasicBlock *Target : successors(Exit)) {
+      if (CR->Blocks.count(Target) == 0)
+        ExitTargets.insert(Target);
     }
+  }
 
-    // Creating one constant per distinct exit target. This will be route to the
-    // correct target.
-    DenseMap<BasicBlock *, ConstantInt *> TargetToValue;
-    for (BasicBlock *Target : SortedExitTargets)
-      TargetToValue.insert(
-          std::make_pair(Target, Builder.getInt32(TargetToValue.size())));
-
-    // Creating one variable per exit node, set to the constant matching the
-    // targeted external block.
-    std::vector<std::pair<BasicBlock *, Value *>> ExitToVariable;
-    for (auto Exit : SortedExits) {
-      llvm::Value *Value = createExitVariable(Exit, TargetToValue);
-      IRBuilder<> B2(Exit);
-      B2.SetInsertPoint(Exit->getFirstInsertionPt());
-      B2.CreateStore(Value, Variable);
-      ExitToVariable.emplace_back(std::make_pair(Exit, Value));
-    }
+  // If we have zero or one exit target, nothing do to.
+  if (ExitTargets.size() <= 1)
+    return false;
+
+  // Create the new single exit target.
+  auto F = CR->Entry->getParent();
+  auto NewExitTarget = BasicBlock::Create(F->getContext(), "new.exit", F);
+  IRBuilder<> Builder(NewExitTarget);
+
+  AllocaInst *Variable = createVariable(*F, Builder.getInt32Ty(),
+                                        F->begin()->getFirstInsertionPt());
+
+  // CodeGen output needs to be stable. Using the set as-is would order
+  // the targets differently depending on the allocation pattern.
+  // Sorting per basic-block ordering in the function.
+  std::vector<BasicBlock *> SortedExitTargets;
+  std::vector<BasicBlock *> SortedExits;
+  for (BasicBlock &BB : *F) {
+    if (ExitTargets.count(&BB) != 0)
+      SortedExitTargets.push_back(&BB);
+    if (CR->Exits.count(&BB) != 0)
+      SortedExits.push_back(&BB);
+  }
 
-    llvm::Value *Load = Builder.CreateLoad(Builder.getInt32Ty(), Variable);
+  // Creating one constant per distinct exit target. This will be route to the
+  // correct target.
+  DenseMap<BasicBlock *, ConstantInt *> TargetToValue;
+  for (BasicBlock *Target : SortedExitTargets)
+    TargetToValue.insert(
+        std::make_pair(Target, Builder.getInt32(TargetToValue.size())));
+
+  // Creating one variable per exit node, set to the constant matching the
+  // targeted external block.
+  std::vector<std::pair<BasicBlock *, Value *>> ExitToVariable;
+  for (auto Exit : SortedExits) {
+    llvm::Value *Value = createExitVariable(Exit, TargetToValue);
+    IRBuilder<> B2(Exit);
+    B2.SetInsertPoint(Exit->getFirstInsertionPt());
+    B2.CreateStore(Value, Variable);
+    ExitToVariable.emplace_back(std::make_pair(Exit, Value));
+  }
 
-    // Creating the switch to jump to the correct exit target.
-    llvm::SwitchInst *Sw = Builder.CreateSwitch(Load, SortedExitTargets[0],
-                                                SortedExitTargets.size() - 1);
-    for (size_t i = 1; i < SortedExitTargets.size(); i++) {
-      BasicBlock *BB = SortedExitTargets[i];
-      Sw->addCase(TargetToValue[BB], BB);
-    }
+  llvm::Value *Load = Builder.CreateLoad(Builder.getInt32Ty(), Variable);
 
-    // Fix exit branches to redirect to the new exit.
-    for (auto Exit : CR->Exits) {
-      Instruction *T = Exit->getTerminator();
-      for (auto I = succ_begin(T), E = succ_end(T); I != E; ++I)
-        if (ExitTargets.contains(*I))
-          I.getUse()->set(NewExitTarget);
-    }
+  // Creating the switch to jump to the correct exit target.
+  llvm::SwitchInst *Sw = Builder.CreateSwitch(Load, SortedExitTargets[0],
+                                              SortedExitTargets.size() - 1);
+  for (size_t i = 1; i < SortedExitTargets.size(); i++) {
+    BasicBlock *BB = SortedExitTargets[i];
+    Sw->addCase(TargetToValue[BB], BB);
+  }
 
-    CR = CR->Parent;
-    while (CR) {
-      CR->Blocks.insert(NewExitTarget);
-      CR = CR->Parent;
-    }
+  // Fix exit branches to redirect to the new exit.
+  for (auto Exit : CR->Exits) {
+    Instruction *T = Exit->getTerminator();
+    for (auto I = succ_begin(T), E = succ_end(T); I != E; ++I)
+      if (ExitTargets.contains(*I))
+        I.getUse()->set(NewExitTarget);
+  }
 
-    return true;
+  CR = CR->Parent;
+  while (CR) {
+    CR->Blocks.insert(NewExitTarget);
+    CR = CR->Parent;
   }
 
-  /// Run the pass on the given convergence region and sub-regions (DFS).
-  /// Returns true if a region/sub-region was modified, false otherwise.
-  /// This returns as soon as one region/sub-region has been modified.
-  bool runOnConvergenceRegion(LoopInfo &LI, SPIRV::ConvergenceRegion *CR) {
-    for (auto *Child : CR->Children)
-      if (runOnConvergenceRegion(LI, Child))
-        return true;
+  return true;
+}
 
-    return runOnConvergenceRegionNoRecurse(LI, CR);
-  }
+/// Run the pass on the given convergence region and sub-regions (DFS).
+/// Returns true if a region/sub-region was modified, false otherwise.
+/// This returns as soon as one region/sub-region has been modified.
+static bool runOnConvergenceRegion(LoopInfo &LI,
+                                   SPIRV::ConvergenceRegion *CR) {
+  for (auto *Child : CR->Children)
+    if (runOnConvergenceRegion(LI, Child))
+      return true;
+
+  return runOnConvergenceRegionNoRecurse(LI, CR);
+}
 
 #if !NDEBUG
-  /// Validates each edge exiting the region has the same destination basic
-  /// block.
-  void validateRegionExits(const SPIRV::ConvergenceRegion *CR) {
-    for (auto *Child : CR->Children)
-      validateRegionExits(Child);
-
-    std::unordered_set<BasicBlock *> ExitTargets;
-    for (auto *Exit : CR->Exits) {
-      for (auto *BB : successors(Exit)) {
-        if (CR->Blocks.count(BB) == 0)
-          ExitTargets.insert(BB);
-      }
+/// Validates each edge exiting the region has the same destination basic
+/// block.
+static void validateRegionExits(const SPIRV::ConvergenceRegion *CR) {
+  for (auto *Child : CR->Children)
+    validateRegionExits(Child);
+
+  std::unordered_set<BasicBlock *> ExitTargets;
+  for (auto *Exit : CR->Exits) {
+    for (auto *BB : successors(Exit)) {
+      if (CR->Blocks.count(BB) == 0)
+        ExitTargets.insert(BB);
     }
-
-    assert(ExitTargets.size() <= 1);
   }
+
+  assert(ExitTargets.size() <= 1);
+}
 #endif
 
-  bool runOnFunction(Function &F) override {
-    LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
-    auto *TopLevelRegion =
-        getAnalysis<SPIRVConvergenceRegionAnalysisWrapperPass>()
-            .getRegionInfo()
-            .getWritableTopLevelRegion();
-
-    // FIXME: very inefficient method: each time a region is modified, we bubble
-    // back up, and recompute the whole convergence region tree. Once the
-    // algorithm is completed and test coverage good enough, rewrite this pass
-    // to be efficient instead of simple.
-    bool modified = false;
-    while (runOnConvergenceRegion(LI, TopLevelRegion)) {
-      modified = true;
-    }
+static bool runImpl(Function &F, LoopInfo &LI,
+                    SPIRV::ConvergenceRegionInfo &RegionInfo) {
+  auto *TopLevelRegion = RegionInfo.getWritableTopLevelRegion();
+
+  // FIXME: very inefficient method: each time a region is modified, we bubble
+  // back up, and recompute the whole convergence region tree. Once the
+  // algorithm is completed and test coverage good enough, rewrite this pass
+  // to be efficient instead of simple.
+  bool Modified = false;
+  while (runOnConvergenceRegion(LI, TopLevelRegion)) {
+    Modified = true;
+  }
 
 #if !defined(NDEBUG) || defined(EXPENSIVE_CHECKS)
-    validateRegionExits(TopLevelRegion);
+  validateRegionExits(TopLevelRegion);
 #endif
-    return modified;
+  return Modified;
+}
+
+class SPIRVMergeRegionExitTargetsLegacy : public FunctionPass {
+public:
+  static char ID;
+
+  SPIRVMergeRegionExitTargetsLegacy() : FunctionPass(ID) {}
+
+  bool runOnFunction(Function &F) override {
+    LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
+    auto &RegionInfo =
+        getAnalysis<SPIRVConvergenceRegionAnalysisWrapperPass>().getRegionInfo();
+    return runImpl(F, LI, RegionInfo);
   }
 
   void getAnalysisUsage(AnalysisUsage &AU) const override {
@@ -216,18 +222,28 @@ class SPIRVMergeRegionExitTargets : public FunctionPass {
 };
 } // namespace
 
-char SPIRVMergeRegionExitTargets::ID = 0;
+PreservedAnalyses
+SPIRVMergeRegionExitTargets::run(Function &F, FunctionAnalysisManager &AM) {
+  auto &LI = AM.getResult<LoopAnalysis>(F);
+  auto &RegionInfo = AM.getResult<SPIRVConvergenceRegionAnalysis>(F);
+  return runImpl(F, LI, RegionInfo) ? PreservedAnalyses::none()
+                                    : PreservedAnalyses::all();
+}
+
+char SPIRVMergeRegionExitTargetsLegacy::ID = 0;
 
-INITIALIZE_PASS_BEGIN(SPIRVMergeRegionExitTargets, "split-region-exit-blocks",
+INITIALIZE_PASS_BEGIN(SPIRVMergeRegionExitTargetsLegacy,
+                      "split-region-exit-blocks",
                       "SPIRV split region exit blocks", false, false)
 INITIALIZE_PASS_DEPENDENCY(LoopSimplify)
 INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass)
 INITIALIZE_PASS_DEPENDENCY(LoopInfoWrapperPass)
 INITIALIZE_PASS_DEPENDENCY(SPIRVConvergenceRegionAnalysisWrapperPass)
 
-INITIALIZE_PASS_END(SPIRVMergeRegionExitTargets, "split-region-exit-blocks",
+INITIALIZE_PASS_END(SPIRVMergeRegionExitTargetsLegacy,
+                    "split-region-exit-blocks",
                     "SPIRV split region exit blocks", false, false)
 
 FunctionPass *llvm::createSPIRVMergeRegionExitTargetsPass() {
-  return new SPIRVMergeRegionExitTargets();
+  return new SPIRVMergeRegionExitTargetsLegacy();
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.h b/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.h
new file mode 100644
index 0000000000000..c8a710cf82a6b
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVMergeRegionExitTargets.h
@@ -0,0 +1,24 @@
+//===-- SPIRVMergeRegionExitTargets.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVMERGEREGIONEXITTARGETS_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVMERGEREGIONEXITTARGETS_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class SPIRVMergeRegionExitTargets
+    : public PassInfoMixin<SPIRVMergeRegionExitTargets> {
+public:
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVMERGEREGIONEXITTARGETS_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
index 39f18cf8218a4..2ffaa9d4c2200 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
+++ b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
@@ -17,7 +17,9 @@
 #define MODULE_PASS(NAME, CREATE_PASS)
 #endif
 MODULE_PASS("spirv-cbuffer-access", SPIRVCBufferAccess())
+MODULE_PASS("spirv-legalize-implicit-binding", SPIRVLegalizeImplicitBinding())
 MODULE_PASS("spirv-legalize-zero-size-arrays", SPIRVLegalizeZeroSizeArrays(*static_cast<const SPIRVTargetMachine *>(this)))
+MODULE_PASS("spirv-lower-ctor-dtor", SPIRVCtorDtorLoweringPass())
 MODULE_PASS("spirv-pushconstant-access", SPIRVPushConstantAccess(*static_cast<const SPIRVTargetMachine *>(this)))
 MODULE_PASS("spirv-emit-intrinsics", SPIRVEmitIntrinsicsPass(*static_cast<const SPIRVTargetMachine *>(this)))
 #undef MODULE_PASS
@@ -25,5 +27,14 @@ MODULE_PASS("spirv-emit-intrinsics", SPIRVEmitIntrinsicsPass(*static_cast<const
 #ifndef FUNCTION_PASS
 #define FUNCTION_PASS(NAME, CREATE_PASS)
 #endif
+FUNCTION_PASS("spirv-legalize-pointer-cast", SPIRVLegalizePointerCast(*static_cast<const SPIRVTargetMachine *>(this)))
+FUNCTION_PASS("spirv-merge-region-exits", SPIRVMergeRegionExitTargets())
+FUNCTION_PASS("spirv-regularizer", SPIRVRegularizer())
 FUNCTION_PASS("spirv-structurizer", SPIRVStructurizerWrapper())
 #undef FUNCTION_PASS
+
+#ifndef FUNCTION_ANALYSIS
+#define FUNCTION_ANALYSIS(NAME, CREATE_PASS)
+#endif
+FUNCTION_ANALYSIS("spirv-convergence-region", SPIRVConvergenceRegionAnalysis())
+#undef FUNCTION_ANALYSIS
diff --git a/llvm/lib/Target/SPIRV/SPIRVRegularizer.cpp b/llvm/lib/Target/SPIRV/SPIRVRegularizer.cpp
index c864556783a2a..bcfae91b63da2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVRegularizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVRegularizer.cpp
@@ -11,6 +11,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "SPIRVRegularizer.h"
 #include "SPIRV.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/IRBuilder.h"
@@ -24,27 +25,25 @@
 
 using namespace llvm;
 
+static bool runImpl(Function &F);
+
 namespace {
-struct SPIRVRegularizer : public FunctionPass {
+struct SPIRVRegularizerLegacy : public FunctionPass {
 public:
   static char ID;
-  SPIRVRegularizer() : FunctionPass(ID) {}
-  bool runOnFunction(Function &F) override;
+  SPIRVRegularizerLegacy() : FunctionPass(ID) {}
+  bool runOnFunction(Function &F) override { return runImpl(F); }
   StringRef getPassName() const override { return "SPIR-V Regularizer"; }
 
   void getAnalysisUsage(AnalysisUsage &AU) const override {
     FunctionPass::getAnalysisUsage(AU);
   }
-
-private:
-  void runLowerConstExpr(Function &F);
-  void runLowerI1Comparisons(Function &F);
 };
 } // namespace
 
-char SPIRVRegularizer::ID = 0;
+char SPIRVRegularizerLegacy::ID = 0;
 
-INITIALIZE_PASS(SPIRVRegularizer, DEBUG_TYPE, "SPIR-V Regularizer", false,
+INITIALIZE_PASS(SPIRVRegularizerLegacy, DEBUG_TYPE, "SPIR-V Regularizer", false,
                 false)
 
 // Since SPIR-V cannot represent constant expression, constant expressions
@@ -55,7 +54,7 @@ INITIALIZE_PASS(SPIRVRegularizer, DEBUG_TYPE, "SPIR-V Regularizer", false,
 // and all uses of it by instructions in that function are replaced by
 // one instruction.
 // TODO: remove redundant instructions for common subexpression.
-void SPIRVRegularizer::runLowerConstExpr(Function &F) {
+static void runLowerConstExpr(Function &F) {
   LLVMContext &Ctx = F.getContext();
   std::list<Instruction *> WorkList;
   for (auto &II : instructions(F))
@@ -157,7 +156,7 @@ void SPIRVRegularizer::runLowerConstExpr(Function &F) {
 // The backend treats i1 as boolean values, and SPIR-V only allows logical
 // operations for boolean values. This function lowers i1 comparisons with
 // certain predicates to logical operations to generate valid SPIR-V.
-void SPIRVRegularizer::runLowerI1Comparisons(Function &F) {
+static void runLowerI1Comparisons(Function &F) {
   for (auto &I : make_early_inc_range(instructions(F))) {
     auto *Cmp = dyn_cast<ICmpInst>(&I);
     if (!Cmp)
@@ -209,12 +208,17 @@ void SPIRVRegularizer::runLowerI1Comparisons(Function &F) {
   }
 }
 
-bool SPIRVRegularizer::runOnFunction(Function &F) {
+static bool runImpl(Function &F) {
   runLowerI1Comparisons(F);
   runLowerConstExpr(F);
   return true;
 }
 
+PreservedAnalyses SPIRVRegularizer::run(Function &F,
+                                       FunctionAnalysisManager &AM) {
+  return runImpl(F) ? PreservedAnalyses::none() : PreservedAnalyses::all();
+}
+
 FunctionPass *llvm::createSPIRVRegularizerPass() {
-  return new SPIRVRegularizer();
+  return new SPIRVRegularizerLegacy();
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVRegularizer.h b/llvm/lib/Target/SPIRV/SPIRVRegularizer.h
new file mode 100644
index 0000000000000..6daa1d51a8344
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVRegularizer.h
@@ -0,0 +1,23 @@
+//===-- SPIRVRegularizer.h - regularize IR for SPIR-V -----------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_SPIRV_SPIRVREGULARIZER_H
+#define LLVM_LIB_TARGET_SPIRV_SPIRVREGULARIZER_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class SPIRVRegularizer : public PassInfoMixin<SPIRVRegularizer> {
+public:
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVREGULARIZER_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 54dfbc6937d6a..01ec003703b4a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -11,13 +11,19 @@
 //===----------------------------------------------------------------------===//
 
 #include "SPIRVTargetMachine.h"
+#include "Analysis/SPIRVConvergenceRegionAnalysis.h"
 #include "SPIRV.h"
 #include "SPIRVCBufferAccess.h"
+#include "SPIRVCtorDtorLowering.h"
 #include "SPIRVEmitIntrinsics.h"
 #include "SPIRVGlobalRegistry.h"
+#include "SPIRVLegalizeImplicitBinding.h"
+#include "SPIRVLegalizePointerCast.h"
 #include "SPIRVLegalizeZeroSizeArrays.h"
 #include "SPIRVLegalizerInfo.h"
+#include "SPIRVMergeRegionExitTargets.h"
 #include "SPIRVPushConstantAccess.h"
+#include "SPIRVRegularizer.h"
 #include "SPIRVStructurizerWrapper.h"
 #include "SPIRVTargetObjectFile.h"
 #include "SPIRVTargetTransformInfo.h"
@@ -56,15 +62,16 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
   initializeSPIRVCBufferAccessLegacyPass(PR);
   initializeSPIRVPushConstantAccessLegacyPass(PR);
   initializeSPIRVPreLegalizerCombinerPass(PR);
-  initializeSPIRVLegalizePointerCastPass(PR);
+  initializeSPIRVLegalizePointerCastLegacyPass(PR);
   initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PR);
-  initializeSPIRVRegularizerPass(PR);
+  initializeSPIRVRegularizerLegacyPass(PR);
   initializeSPIRVPreLegalizerPass(PR);
   initializeSPIRVPostLegalizerPass(PR);
-  initializeSPIRVMergeRegionExitTargetsPass(PR);
+  initializeSPIRVMergeRegionExitTargetsLegacyPass(PR);
   initializeSPIRVEmitIntrinsicsPass(PR);
   initializeSPIRVPrepareFunctionsPass(PR);
   initializeSPIRVPrepareGlobalsPass(PR);
+  initializeSPIRVLegalizeImplicitBindingLegacyPass(PR);
   initializeSPIRVCtorDtorLoweringLegacyPass(PR);
 }
 
diff --git a/llvm/test/CodeGen/SPIRV/ctor-dtor-lowering-ir.ll b/llvm/test/CodeGen/SPIRV/ctor-dtor-lowering-ir.ll
index 84d8375ab1310..a234069bb8917 100644
--- a/llvm/test/CodeGen/SPIRV/ctor-dtor-lowering-ir.ll
+++ b/llvm/test/CodeGen/SPIRV/ctor-dtor-lowering-ir.ll
@@ -1,5 +1,6 @@
 ; Test for SPIR-V constructor/destructor lowering pass at IR level.
 ; RUN: llc -mtriple=spirv64-intel-unknown --spirv-ext=+SPV_INTEL_function_pointers %s -o /dev/null -print-after=spirv-lower-ctor-dtor 2>&1 | FileCheck %s
+; RUN: opt -S -passes=spirv-lower-ctor-dtor -mtriple=spirv64-intel-unknown < %s | FileCheck %s
 
 ; This test verifies that:
 ; 1. The init/fini kernels are created.
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
new file mode 100644
index 0000000000000..eb0d0311bedf0
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizeImplicitBinding.ll
@@ -0,0 +1,20 @@
+; RUN: opt -S -passes=spirv-legalize-implicit-binding -mtriple=spirv1.6-vulkan1.3-library < %s | FileCheck %s
+
+ at .str.b = private unnamed_addr constant [2 x i8] c"b\00", align 1
+ at .str.c = private unnamed_addr constant [2 x i8] c"c\00", align 1
+
+; Verify implicit-binding intrinsic calls are rewritten to explicit
+; handlefrombinding intrinsic calls; the descriptor set is preserved
+; (1st operand of the original) and a new binding number is assigned.
+
+define void @main() local_unnamed_addr #0 {
+entry:
+; CHECK-LABEL: define void @main(
+; CHECK: call target({{.*}}) @llvm.spv.resource.handlefrombinding{{.*}}(i32 0, i32 0, i32 1, i32 0, ptr {{.*}}@.str.b)
+; CHECK: call target({{.*}}) @llvm.spv.resource.handlefrombinding{{.*}}(i32 1, i32 0, i32 1, i32 0, ptr {{.*}}@.str.c)
+  %0 = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 0, i32 0, i32 1, i32 0, ptr nonnull @.str.b)
+  %1 = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 1, i32 1, i32 1, i32 0, ptr nonnull @.str.c)
+  ret void
+}
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
new file mode 100644
index 0000000000000..3773a42ece424
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVLegalizePointerCast.ll
@@ -0,0 +1,30 @@
+; spirv-legalize-pointer-cast consumes spv.ptrcast intrinsics produced by
+; spirv-emit-intrinsics, so we chain both passes and check the ptrcast is
+; rewritten into a sequence of typed loads + gep/extractelt.
+;
+; RUN: opt -S -passes='spirv-emit-intrinsics,function(spirv-legalize-pointer-cast)' -mtriple=spirv-unknown-vulkan-compute < %s | FileCheck %s
+
+target triple = "spirv-unknown-vulkan1.3-compute"
+
+ at M = internal addrspace(10) global [4 x <2 x float>] zeroinitializer, align 4
+ at OUT = internal addrspace(10) global float zeroinitializer, align 4
+
+; Loading a <5 x float> through a [4 x <2 x float>] forces emit-intrinsics to
+; insert spv.ptrcast; legalize-pointer-cast lowers it to typed <2 x float>
+; loads stitched together with extractelt. After the pass, no spv.ptrcast call
+; should remain.
+
+define spir_func void @main() #0 {
+; CHECK-LABEL: define spir_func void @main(
+; CHECK-NOT: call {{.*}}@llvm.spv.ptrcast
+; CHECK: call ptr addrspace(10) {{.*}}@llvm.spv.gep.p10.p10(i1 false, ptr addrspace(10) @M, i32 0, i32 0)
+; CHECK: load <2 x float>, ptr addrspace(10)
+; CHECK: call float @llvm.spv.extractelt.f32.v2f32.i32(<2 x float>
+entry:
+  %v = load <5 x float>, ptr addrspace(10) @M, align 4
+  %x = extractelement <5 x float> %v, i32 4
+  store float %x, ptr addrspace(10) @OUT, align 4
+  ret void
+}
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVMergeRegionExitTargets.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVMergeRegionExitTargets.ll
new file mode 100644
index 0000000000000..866fcb6371d8b
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVMergeRegionExitTargets.ll
@@ -0,0 +1,46 @@
+; RUN: opt -S -passes=spirv-merge-region-exits -mtriple=spirv-unknown-vulkan-compute < %s | FileCheck %s
+
+target triple = "spirv-unknown-vulkan1.3-compute"
+
+; A loop with two distinct exit targets (exit_a, exit_b). The pass
+; should merge them into a single new.exit block dispatched via a
+; switch on a stored discriminator value.
+
+define spir_func i32 @two_exits(i1 %ca, i1 %cb) #0 {
+; CHECK-LABEL: define spir_func i32 @two_exits(
+; CHECK:       entry:
+; CHECK:         [[REG:%.*]] = alloca i32
+; CHECK:       loop:
+; CHECK:         store i32 {{.*}}, ptr [[REG]]
+; CHECK:         br i1 %ca, label %new.exit, label %body
+; CHECK:       body:
+; CHECK:         store i32 {{.*}}, ptr [[REG]]
+; CHECK:         br i1 %cb, label %new.exit, label %loop
+; CHECK:       new.exit:
+; CHECK:         [[V:%.*]] = load i32, ptr [[REG]]
+; CHECK:         switch i32 [[V]], label %exit_a [
+; CHECK:           i32 {{.*}}, label %exit_b
+; CHECK:         ]
+entry:
+  %t0 = call token @llvm.experimental.convergence.entry()
+  br label %loop
+
+loop:
+  %t1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %t0) ]
+  br i1 %ca, label %exit_a, label %body
+
+body:
+  br i1 %cb, label %exit_b, label %loop
+
+exit_a:
+  ret i32 1
+
+exit_b:
+  ret i32 2
+}
+
+declare token @llvm.experimental.convergence.entry() #1
+declare token @llvm.experimental.convergence.loop() #1
+
+attributes #0 = { convergent noinline nounwind }
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
diff --git a/llvm/test/CodeGen/SPIRV/passes/SPIRVRegularizer-i1-icmp.ll b/llvm/test/CodeGen/SPIRV/passes/SPIRVRegularizer-i1-icmp.ll
new file mode 100644
index 0000000000000..361a422c425c3
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/passes/SPIRVRegularizer-i1-icmp.ll
@@ -0,0 +1,24 @@
+; RUN: opt -S -passes=spirv-regularizer -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+
+; Verify that i1 ICMP comparisons are lowered to logical operations,
+; matching the legacy pass behavior in runLowerI1Comparisons.
+
+define i1 @ult_i1(i1 %p, i1 %q) {
+; CHECK-LABEL: define i1 @ult_i1(
+; CHECK-SAME: i1 [[P:%.*]], i1 [[Q:%.*]]) {
+; CHECK:    [[NOT:%.*]] = xor i1 [[P]], true
+; CHECK:    [[R:%.*]] = and i1 [[Q]], [[NOT]]
+; CHECK:    ret i1 [[R]]
+  %r = icmp ult i1 %p, %q
+  ret i1 %r
+}
+
+define i1 @ugt_i1(i1 %p, i1 %q) {
+; CHECK-LABEL: define i1 @ugt_i1(
+; CHECK-SAME: i1 [[P:%.*]], i1 [[Q:%.*]]) {
+; CHECK:    [[NOT:%.*]] = xor i1 [[Q]], true
+; CHECK:    [[R:%.*]] = and i1 [[P]], [[NOT]]
+; CHECK:    ret i1 [[R]]
+  %r = icmp ugt i1 %p, %q
+  ret i1 %r
+}



More information about the llvm-commits mailing list