[llvm] [SPIRV] Add pass to replace gethandlefromimplicitbinding (PR #146756)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 23 11:08:41 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-spir-v

Author: Steven Perron (s-perron)

<details>
<summary>Changes</summary>

The HLSL frontend generates call to the intrinsic
@<!-- -->llvm.spv.resource.handlefromimplicitbinding to be able to access a
resource where the set and binding were not explicitly given in the
source code. Determining the correct set and binding cannot be done
during Clang's codegen or earlier because in DXIL, they must first
remove resource that are not accessed before assigning binding locations
to the resource without an explicit binding.

We will follow their lead.

This is a change from DXC, where implicit binding for SPIR-V are
assigned before optimizations.

See https://github.com/llvm/wg-hlsl/pull/309

---
Full diff: https://github.com/llvm/llvm-project/pull/146756.diff


5 Files Affected:

- (modified) llvm/lib/Target/SPIRV/CMakeLists.txt (+1) 
- (modified) llvm/lib/Target/SPIRV/SPIRV.h (+2) 
- (added) llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp (+159) 
- (modified) llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp (+1) 
- (added) llvm/test/CodeGen/SPIRV/hlsl-resources/ImplicitBinding.ll (+75) 


``````````diff
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index ba09451104479..6660de995e954 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -26,6 +26,7 @@ add_llvm_target(SPIRVCodeGen
   SPIRVGlobalRegistry.cpp
   SPIRVInstrInfo.cpp
   SPIRVInstructionSelector.cpp
+  SPIRVLegalizeImplicitBinding.cpp
   SPIRVStripConvergentIntrinsics.cpp
   SPIRVLegalizePointerCast.cpp
   SPIRVMergeRegionExitTargets.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 1688fa32ce436..1934e98ca512f 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -23,6 +23,7 @@ ModulePass *createSPIRVPrepareFunctionsPass(const SPIRVTargetMachine &TM);
 FunctionPass *createSPIRVStructurizerPass();
 FunctionPass *createSPIRVMergeRegionExitTargetsPass();
 FunctionPass *createSPIRVStripConvergenceIntrinsicsPass();
+ModulePass *createSPIRVLegalizeImplicitBindingPass();
 FunctionPass *createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM);
 FunctionPass *createSPIRVRegularizerPass();
 FunctionPass *createSPIRVPreLegalizerCombiner();
@@ -49,6 +50,7 @@ void initializeSPIRVRegularizerPass(PassRegistry &);
 void initializeSPIRVMergeRegionExitTargetsPass(PassRegistry &);
 void initializeSPIRVPrepareFunctionsPass(PassRegistry &);
 void initializeSPIRVStripConvergentIntrinsicsPass(PassRegistry &);
+void initializeSPIRVLegalizeImplicitBindingPass(PassRegistry &);
 } // namespace llvm
 
 #endif // LLVM_LIB_TARGET_SPIRV_SPIRV_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
new file mode 100644
index 0000000000000..0da9b633d3c03
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeImplicitBinding.cpp
@@ -0,0 +1,159 @@
+//===- SPIRVLegalizeImplicitBinding.cpp - 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass legalizes the @llvm.spv.resource.handlefromimplicitbinding
+// intrinsic by replacing it with a call to
+// @llvm.spv.resource.handlefrombinding.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRV.h"
+#include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Pass.h"
+#include <algorithm>
+#include <vector>
+
+using namespace llvm;
+
+namespace {
+class SPIRVLegalizeImplicitBinding : public ModulePass {
+public:
+  static char ID;
+  SPIRVLegalizeImplicitBinding() : ModulePass(ID) {}
+
+  bool runOnModule(Module &M) override;
+
+private:
+  void collectBindingInfo(Module &M);
+  uint32_t getAndReserveFirstUnusedBinding(uint32_t DescSet);
+  void replaceImplicitBindingCalls(Module &M);
+
+  // A map from descriptor set to a bit vector of used binding numbers.
+  std::vector<BitVector> UsedBindings;
+  // A list of all implicit binding calls, to be sorted by order ID.
+  SmallVector<CallInst *, 16> ImplicitBindingCalls;
+};
+
+struct BindingInfoCollector : public InstVisitor<BindingInfoCollector> {
+  std::vector<BitVector> &UsedBindings;
+  SmallVector<CallInst *, 16> &ImplicitBindingCalls;
+
+  BindingInfoCollector(std::vector<BitVector> &UsedBindings,
+                       SmallVector<CallInst *, 16> &ImplicitBindingCalls)
+      : UsedBindings(UsedBindings), ImplicitBindingCalls(ImplicitBindingCalls) {
+  }
+
+  void visitCallInst(CallInst &CI) {
+    if (CI.getIntrinsicID() == Intrinsic::spv_resource_handlefrombinding) {
+      const uint32_t DescSet =
+          cast<ConstantInt>(CI.getArgOperand(0))->getZExtValue();
+      const uint32_t Binding =
+          cast<ConstantInt>(CI.getArgOperand(1))->getZExtValue();
+
+      if (UsedBindings.size() <= DescSet) {
+        UsedBindings.resize(DescSet + 1);
+        UsedBindings[DescSet].resize(64);
+      }
+      if (UsedBindings[DescSet].size() <= Binding) {
+        UsedBindings[DescSet].resize(2 * Binding);
+      }
+      UsedBindings[DescSet].set(Binding);
+    } else if (CI.getIntrinsicID() ==
+               Intrinsic::spv_resource_handlefromimplicitbinding) {
+      ImplicitBindingCalls.push_back(&CI);
+    }
+  }
+};
+
+void SPIRVLegalizeImplicitBinding::collectBindingInfo(Module &M) {
+  BindingInfoCollector InfoCollector(UsedBindings, ImplicitBindingCalls);
+  InfoCollector.visit(M);
+
+  // Sort the collected calls by their order ID.
+  std::sort(
+      ImplicitBindingCalls.begin(), ImplicitBindingCalls.end(),
+      [](const CallInst *A, const CallInst *B) {
+        const uint32_t OrderIdArgIdx = 0;
+        const uint32_t OrderA =
+            cast<ConstantInt>(A->getArgOperand(OrderIdArgIdx))->getZExtValue();
+        const uint32_t OrderB =
+            cast<ConstantInt>(B->getArgOperand(OrderIdArgIdx))->getZExtValue();
+        return OrderA < OrderB;
+      });
+}
+
+uint32_t SPIRVLegalizeImplicitBinding::getAndReserveFirstUnusedBinding(
+    uint32_t DescSet) {
+  if (UsedBindings.size() <= DescSet) {
+    UsedBindings.resize(DescSet + 1);
+    UsedBindings[DescSet].resize(64);
+  }
+
+  int NewBinding = UsedBindings[DescSet].find_first_unset();
+  if (NewBinding == -1) {
+    NewBinding = UsedBindings[DescSet].size();
+    UsedBindings[DescSet].resize(2 * NewBinding + 1);
+  }
+
+  UsedBindings[DescSet].set(NewBinding);
+  return NewBinding;
+}
+
+void SPIRVLegalizeImplicitBinding::replaceImplicitBindingCalls(Module &M) {
+  for (CallInst *OldCI : ImplicitBindingCalls) {
+    IRBuilder<> Builder(OldCI);
+    const uint32_t DescSet =
+        cast<ConstantInt>(OldCI->getArgOperand(1))->getZExtValue();
+    const uint32_t NewBinding = getAndReserveFirstUnusedBinding(DescSet);
+
+    SmallVector<Value *, 8> Args;
+    Args.push_back(Builder.getInt32(DescSet));
+    Args.push_back(Builder.getInt32(NewBinding));
+
+    // Copy the remaining arguments from the old call.
+    for (uint32_t i = 2; i < OldCI->arg_size(); ++i) {
+      Args.push_back(OldCI->getArgOperand(i));
+    }
+
+    Function *NewFunc = Intrinsic::getOrInsertDeclaration(
+        &M, Intrinsic::spv_resource_handlefrombinding, OldCI->getType());
+    CallInst *NewCI = Builder.CreateCall(NewFunc, Args);
+    NewCI->setCallingConv(OldCI->getCallingConv());
+
+    OldCI->replaceAllUsesWith(NewCI);
+    OldCI->eraseFromParent();
+  }
+}
+
+bool SPIRVLegalizeImplicitBinding::runOnModule(Module &M) {
+  collectBindingInfo(M);
+  if (ImplicitBindingCalls.empty()) {
+    return false;
+  }
+
+  replaceImplicitBindingCalls(M);
+  return true;
+}
+} // namespace
+
+char SPIRVLegalizeImplicitBinding::ID = 0;
+
+INITIALIZE_PASS(SPIRVLegalizeImplicitBinding, "legalize-spirv-implicit-binding",
+                "Legalize SPIR-V implicit bindings", false, false)
+
+ModulePass *llvm::createSPIRVLegalizeImplicitBindingPass() {
+  return new SPIRVLegalizeImplicitBinding();
+}
\ No newline at end of file
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index d7cf211ba84dc..e0bfb77f4b530 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -226,6 +226,7 @@ void SPIRVPassConfig::addIRPasses() {
 }
 
 void SPIRVPassConfig::addISelPrepare() {
+  addPass(createSPIRVLegalizeImplicitBindingPass());
   addPass(createSPIRVEmitIntrinsicsPass(&getTM<SPIRVTargetMachine>()));
   if (TM.getSubtargetImpl()->isLogicalSPIRV())
     addPass(createSPIRVLegalizePointerCastPass(&getTM<SPIRVTargetMachine>()));
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/ImplicitBinding.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/ImplicitBinding.ll
new file mode 100644
index 0000000000000..00e9185822ad5
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/ImplicitBinding.ll
@@ -0,0 +1,75 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv1.6-vulkan1.3-library %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv1.6-vulkan1.3-library %s -o - -filetype=obj | spirv-val --target-env vulkan1.3 %}
+
+ at .str = private unnamed_addr constant [2 x i8] c"b\00", align 1
+ at .str.2 = private unnamed_addr constant [2 x i8] c"c\00", align 1
+ at .str.4 = private unnamed_addr constant [2 x i8] c"d\00", align 1
+ at .str.6 = private unnamed_addr constant [2 x i8] c"e\00", align 1
+ at .str.8 = private unnamed_addr constant [2 x i8] c"f\00", align 1
+ at .str.10 = private unnamed_addr constant [2 x i8] c"g\00", align 1
+ at .str.12 = private unnamed_addr constant [2 x i8] c"h\00", align 1
+ at .str.14 = private unnamed_addr constant [2 x i8] c"i\00", align 1
+
+; CHECK-DAG: OpName [[b:%[0-9]+]] "b"
+; CHECK-DAG: OpName [[c:%[0-9]+]] "c"
+; CHECK-DAG: OpName [[d:%[0-9]+]] "d"
+; CHECK-DAG: OpName [[e:%[0-9]+]] "e"
+; CHECK-DAG: OpName [[f:%[0-9]+]] "f"
+; CHECK-DAG: OpName [[g:%[0-9]+]] "g"
+; CHECK-DAG: OpName [[h:%[0-9]+]] "h"
+; CHECK-DAG: OpName [[i:%[0-9]+]] "i"
+; CHECK-DAG: OpDecorate [[b]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[b]] Binding 1
+; CHECK-DAG: OpDecorate [[c]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[c]] Binding 0
+; CHECK-DAG: OpDecorate [[d]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[d]] Binding 3
+; CHECK-DAG: OpDecorate [[e]] DescriptorSet 0
+; CHECK-DAG: OpDecorate [[e]] Binding 2
+; CHECK-DAG: OpDecorate [[f]] DescriptorSet 10
+; CHECK-DAG: OpDecorate [[f]] Binding 1
+; CHECK-DAG: OpDecorate [[g]] DescriptorSet 10
+; CHECK-DAG: OpDecorate [[g]] Binding 0
+; CHECK-DAG: OpDecorate [[h]] DescriptorSet 10
+; CHECK-DAG: OpDecorate [[h]] Binding 3
+; CHECK-DAG: OpDecorate [[i]] DescriptorSet 10
+; CHECK-DAG: OpDecorate [[i]] Binding 2
+
+
+define void @main() local_unnamed_addr #0 {
+entry:
+  %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, i1 false, ptr nonnull @.str)
+  %1 = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 0, i32 0, i32 1, i32 0, i1 false, ptr nonnull @.str.2)
+  %2 = 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 0, i32 1, i32 0, i1 false, ptr nonnull @.str.4)
+  %3 = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 0, i32 2, i32 1, i32 0, i1 false, ptr nonnull @.str.6)
+  %4 = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 10, i32 1, i32 1, i32 0, i1 false, ptr nonnull @.str.8)
+  %5 = 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 2, i32 10, i32 1, i32 0, i1 false, ptr nonnull @.str.10)
+  %6 = 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 3, i32 10, i32 1, i32 0, i1 false, ptr nonnull @.str.12)
+  %7 = tail call target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) @llvm.spv.resource.handlefrombinding.tspirv.SignedImage_i32_5_2_0_0_2_0t(i32 10, i32 2, i32 1, i32 0, i1 false, ptr nonnull @.str.14)
+  %8 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %1, i32 0)
+  %9 = load i32, ptr addrspace(11) %8, align 4
+  %10 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %2, i32 0)
+  %11 = load i32, ptr addrspace(11) %10, align 4
+  %add.i = add nsw i32 %11, %9
+  %12 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %3, i32 0)
+  %13 = load i32, ptr addrspace(11) %12, align 4
+  %add4.i = add nsw i32 %add.i, %13
+  %14 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %4, i32 0)
+  %15 = load i32, ptr addrspace(11) %14, align 4
+  %add6.i = add nsw i32 %add4.i, %15
+  %16 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %5, i32 0)
+  %17 = load i32, ptr addrspace(11) %16, align 4
+  %add8.i = add nsw i32 %add6.i, %17
+  %18 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %6, i32 0)
+  %19 = load i32, ptr addrspace(11) %18, align 4
+  %add10.i = add nsw i32 %add8.i, %19
+  %20 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %7, i32 0)
+  %21 = load i32, ptr addrspace(11) %20, align 4
+  %add12.i = add nsw i32 %add10.i, %21
+  %22 = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.SignedImage_i32_5_2_0_0_2_0t(target("spirv.SignedImage", i32, 5, 2, 0, 0, 2, 0) %0, i32 0)
+  store i32 %add12.i, ptr addrspace(11) %22, align 4
+  ret void
+}
+
+
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
\ No newline at end of file

``````````

</details>


https://github.com/llvm/llvm-project/pull/146756


More information about the llvm-commits mailing list