[llvm] [hlsl][spirv] Add late pass in spirv backend to remove dead resources (PR #225270)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 21 19:59:55 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-spir-v
Author: rj123-nv
<details>
<summary>Changes</summary>
Add a pass to remove global variables, intended to remove those which are only used to store resources handles to.
These globals may have looked used prior to removing functions unreachable to the selected entry point, due to how clang dxc currently generates handlefrombinding calls in constructors for these resources.
This pass is based on DXILRemoveUnusedResources pass but instead visits globals, as handlefrombinding values may have actual uses in the selected entry point which the handle value is already forwarded to by optimization, which attached test demonstrates.
Assisted by: codex
---
Full diff: https://github.com/llvm/llvm-project/pull/225270.diff
7 Files Affected:
- (modified) llvm/lib/Target/SPIRV/CMakeLists.txt (+1)
- (modified) llvm/lib/Target/SPIRV/SPIRV.h (+9)
- (modified) llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp (+2-1)
- (modified) llvm/lib/Target/SPIRV/SPIRVPassRegistry.def (+1)
- (added) llvm/lib/Target/SPIRV/SPIRVRemoveUnusedResources.cpp (+85)
- (modified) llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp (+2)
- (added) llvm/test/CodeGen/SPIRV/hlsl-resources/remove-unused-resources.ll (+51)
``````````diff
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index ab8b425ba35ca..337821f16d742 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -48,6 +48,7 @@ add_llvm_target(SPIRVCodeGen
SPIRVRegisterInfo.cpp
SPIRVRegularizer.cpp
SPIRVFinalizeShaderLinkage.cpp
+ SPIRVRemoveUnusedResources.cpp
SPIRVSubtarget.cpp
SPIRVTargetMachine.cpp
SPIRVTargetTransformInfo.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index ccc7aa52ec52c..f2492b4a95435 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -42,6 +42,14 @@ class SPIRVStructurizerPass
FunctionPass *createSPIRVStructurizerPass();
+class SPIRVRemoveUnusedResourcesPass
+ : public RequiredPassInfoMixin<SPIRVRemoveUnusedResourcesPass> {
+public:
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+};
+
+ModulePass *createSPIRVRemoveUnusedResourcesLegacyPass();
+
class SPIRVCBufferAccessPass
: public RequiredPassInfoMixin<SPIRVCBufferAccessPass> {
public:
@@ -186,6 +194,7 @@ void initializeSPIRVPreLegalizerCombinerLegacyPass(PassRegistry &);
void initializeSPIRVPostLegalizerLegacyPass(PassRegistry &);
void initializeSPIRVStructurizerPass(PassRegistry &);
void initializeSPIRVCBufferAccessLegacyPass(PassRegistry &);
+void initializeSPIRVRemoveUnusedResourcesLegacyPass(PassRegistry &);
void initializeSPIRVPushConstantAccessLegacyPass(PassRegistry &);
void initializeSPIRVEmitIntrinsicsLegacyPass(PassRegistry &);
void initializeSPIRVLegalizePointerCastLegacyPass(PassRegistry &);
diff --git a/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp b/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp
index 34c8060e97de5..57204f9de7b73 100644
--- a/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVCodeGenPassBuilder.cpp
@@ -88,6 +88,7 @@ void SPIRVCodeGenPassBuilder::addIRPasses(PassManagerWrapper &PMW) {
if (getOptLevel() != CodeGenOptLevel::None) {
addModulePass(SPIRVFinalizeShaderLinkagePass(getTM()), PMW);
}
+ addModulePass(SPIRVRemoveUnusedResourcesPass(), PMW);
} else {
// Variadic function calls aren't supported in shader code.
// This needs to come before SPIRVPrepareFunctions because this
@@ -212,4 +213,4 @@ Error SPIRVTargetMachine::buildCodeGenPipeline(
PassInstrumentationCallbacks *PIC) {
auto CGPB = SPIRVCodeGenPassBuilder(*this, Opt, PIC);
return CGPB.buildPipeline(MPM, MAM, Out, DwoOut, FileType, Ctx);
-}
\ No newline at end of file
+}
diff --git a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
index 4cd3a94b017b4..ffda2d6da9156 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
+++ b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
@@ -27,6 +27,7 @@ MODULE_PASS("spirv-lower-ctor-dtor", SPIRVCtorDtorLoweringPass())
MODULE_PASS("spirv-prepare-functions", SPIRVPrepareFunctionsPass(*static_cast<const SPIRVTargetMachine *>(this)))
MODULE_PASS("spirv-prepare-globals", SPIRVPrepareGlobalsPass())
MODULE_PASS("spirv-pushconstant-access", SPIRVPushConstantAccessPass(*static_cast<const SPIRVTargetMachine *>(this)))
+MODULE_PASS("spirv-remove-unused-resources", SPIRVRemoveUnusedResourcesPass())
#undef MODULE_PASS
#ifndef FUNCTION_PASS
diff --git a/llvm/lib/Target/SPIRV/SPIRVRemoveUnusedResources.cpp b/llvm/lib/Target/SPIRV/SPIRVRemoveUnusedResources.cpp
new file mode 100644
index 0000000000000..11fa61eb1b1a1
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVRemoveUnusedResources.cpp
@@ -0,0 +1,85 @@
+//===- SPIRVRemoveUnusedResources.cpp - Remove unused resources ----------===//
+//
+// 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 Pass for removing unused SPIRV global variables.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRV.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/CommandLine.h"
+
+#define DEBUG_TYPE "spirv-remove-unused-resources"
+
+using namespace llvm;
+
+static cl::opt<bool> DisableSPIRVRemoveUnusedResources(
+ "disable-spirv-remove-unused-resources",
+ cl::desc("Disable spirv-remove-unused-resources pass"), cl::init(false),
+ cl::Hidden);
+
+// Remove module-local globals whose only users, if any, are non-volatile
+// stores.
+static bool removeUnusedResources(Module &M) {
+ if (DisableSPIRVRemoveUnusedResources)
+ return false;
+
+ bool Changed = false;
+ for (GlobalVariable &GV : make_early_inc_range(M.globals())) {
+ if (!GV.hasLocalLinkage())
+ continue;
+
+ if (!all_of(GV.users(), [&GV](User *U) {
+ auto *SI = dyn_cast<StoreInst>(U);
+ return SI && SI->getPointerOperand() == &GV && !SI->isVolatile();
+ }))
+ continue;
+
+ for (User *U : make_early_inc_range(GV.users())) {
+ cast<Instruction>(U)->eraseFromParent();
+ }
+
+ GV.eraseFromParent();
+ Changed = true;
+ }
+
+ return Changed;
+}
+
+PreservedAnalyses
+SPIRVRemoveUnusedResourcesPass::run(Module &M, ModuleAnalysisManager &AM) {
+ return removeUnusedResources(M) ? PreservedAnalyses::none()
+ : PreservedAnalyses::all();
+}
+
+namespace {
+class SPIRVRemoveUnusedResourcesLegacy : public ModulePass {
+public:
+ SPIRVRemoveUnusedResourcesLegacy() : ModulePass(ID) {}
+
+ bool runOnModule(Module &M) override { return removeUnusedResources(M); }
+
+ StringRef getPassName() const override {
+ return "SPIRV Remove Unused Resources";
+ }
+
+ static char ID;
+};
+char SPIRVRemoveUnusedResourcesLegacy::ID = 0;
+} // namespace
+
+INITIALIZE_PASS(SPIRVRemoveUnusedResourcesLegacy, DEBUG_TYPE,
+ "SPIRV Remove Unused Resources", false, false)
+
+ModulePass *llvm::createSPIRVRemoveUnusedResourcesLegacyPass() {
+ return new SPIRVRemoveUnusedResourcesLegacy();
+}
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index b1f70442b26af..b6e1ac89ac2d4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -65,6 +65,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
initializeSPIRVLegalizeResourceBindingLegacyPass(PR);
initializeSPIRVCtorDtorLoweringLegacyPass(PR);
initializeSPIRVFinalizeShaderLinkageLegacyPass(PR);
+ initializeSPIRVRemoveUnusedResourcesLegacyPass(PR);
}
static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
@@ -174,6 +175,7 @@ void SPIRVPassConfig::addIRPasses() {
if (TM.getSubtargetImpl()->isShader()) {
if (getOptLevel() != CodeGenOptLevel::None)
addPass(createSPIRVFinalizeShaderLinkagePass(TM));
+ addPass(createSPIRVRemoveUnusedResourcesLegacyPass());
} else {
// Variadic function calls aren't supported in shader code.
// This needs to come before SPIRVPrepareFunctions because this
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-resources/remove-unused-resources.ll b/llvm/test/CodeGen/SPIRV/hlsl-resources/remove-unused-resources.ll
new file mode 100644
index 0000000000000..faffe63cd4c53
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-resources/remove-unused-resources.ll
@@ -0,0 +1,51 @@
+; RUN: opt -S -passes=spirv-remove-unused-resources %s -o - | FileCheck %s
+
+; For reference, this test contains the IR for the following HLSL
+; after selecting frag_ep1 and just after spirv-finalize-shader-linkage
+;
+; struct Data { float3 color; };
+; ConstantBuffer<Data> data;
+;
+; float4 frag_ep1() : SV_TARGET {
+; return float4(data.color, 1.0);
+; }
+;
+; float4 frag_ep2() : SV_TARGET {
+; return float4(data.color + 0.2, 1.0);
+; }
+;
+; CHECK-NOT: @_ZL4data.0 =
+; CHECK-LABEL: define void @frag_ep1()
+; CHECK: %handle = call target("spirv.VulkanBuffer", %Data, 2, 0) @llvm.spv.resource.handlefromimplicitbinding
+; CHECK-NOT: store target("spirv.VulkanBuffer", %Data, 2, 0) %handle, ptr @_ZL4data.0
+; CHECK: %base = call ptr addrspace(12) @llvm.spv.resource.getbasepointer{{.*}}(target("spirv.VulkanBuffer", %Data, 2, 0) %handle)
+; CHECK: %color = load <3 x float>, ptr addrspace(12) %base
+; CHECK: %extended = shufflevector <3 x float> %color, <3 x float> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 poison>
+; CHECK: %result = insertelement <4 x float> %extended, float 1.000000e+00, i64 3
+; CHECK: store <4 x float> %result, ptr addrspace(8) @SV_TARGET0
+; CHECK-NOT: @_ZL4data.0
+
+target triple = "spirv1.6-unknown-vulkan1.3-pixel"
+
+%Data = type <{ <3 x float> }>
+
+ at _ZL4data.0 = internal unnamed_addr global target("spirv.VulkanBuffer", %Data, 2, 0) poison, align 8
+ at .str = private unnamed_addr constant [5 x i8] c"data\00", align 1
+ at SV_TARGET0 = external hidden thread_local local_unnamed_addr addrspace(8) global <4 x float>
+
+define void @frag_ep1() #0 {
+entry:
+ %handle = call target("spirv.VulkanBuffer", %Data, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s_Datas_2_0t(i32 0, i32 0, i32 1, i32 0, ptr @.str)
+ store target("spirv.VulkanBuffer", %Data, 2, 0) %handle, ptr @_ZL4data.0, align 8
+ %base = call ptr addrspace(12) @llvm.spv.resource.getbasepointer.p12.tspirv.VulkanBuffer_s_Datas_2_0t(target("spirv.VulkanBuffer", %Data, 2, 0) %handle)
+ %color = load <3 x float>, ptr addrspace(12) %base, align 4
+ %extended = shufflevector <3 x float> %color, <3 x float> poison, <4 x i32> <i32 0, i32 1, i32 2, i32 poison>
+ %result = insertelement <4 x float> %extended, float 1.000000e+00, i64 3
+ store <4 x float> %result, ptr addrspace(8) @SV_TARGET0, align 4
+ ret void
+}
+
+declare target("spirv.VulkanBuffer", %Data, 2, 0) @llvm.spv.resource.handlefromimplicitbinding.tspirv.VulkanBuffer_s_Datas_2_0t(i32, i32, i32, i32, ptr)
+declare ptr addrspace(12) @llvm.spv.resource.getbasepointer.p12.tspirv.VulkanBuffer_s_Datas_2_0t(target("spirv.VulkanBuffer", %Data, 2, 0))
+
+attributes #0 = { "hlsl.shader"="pixel" }
``````````
</details>
https://github.com/llvm/llvm-project/pull/225270
More information about the llvm-commits
mailing list