[DirectX] Remove unused heap resources (#218093)

Add handling of `llvm.dx.resource.handlefromheap` to
`DXILRemoveUnusedResources` pass. In practice heap resources are usually
assigned to local variables, and get cleaned up in earlier passes if
unused. If that does not happen for some reason, they get removed by
`DXILRemoveUnusedResources`.

Related to #208053

GitOrigin-RevId: 646f69cafd20589fe3eb08ce04c43a8b914e9181
diff --git a/lib/Target/DirectX/DXILRemoveUnusedResources.cpp b/lib/Target/DirectX/DXILRemoveUnusedResources.cpp
index aa05f58..6a2d0fb 100644
--- a/lib/Target/DirectX/DXILRemoveUnusedResources.cpp
+++ b/lib/Target/DirectX/DXILRemoveUnusedResources.cpp
@@ -33,7 +33,13 @@
 
 using namespace llvm;
 
-// Removes all calls to intrinsics dx_resource_handlefrom{implicit}binding that
+static bool isResourceHandleCreation(Intrinsic::ID ID) {
+  return ID == Intrinsic::dx_resource_handlefrombinding ||
+         ID == Intrinsic::dx_resource_handlefromimplicitbinding ||
+         ID == Intrinsic::dx_resource_handlefromheap;
+}
+
+// Removes all calls to resource handle creation intrinsics that
 // either are not used, or their only use is in a store instruction, which
 // stores the initialized handle into a global variable that does not have
 // external linkage and that is not used anywhere else in the module.
@@ -46,9 +52,7 @@
   for (BasicBlock &BB : make_early_inc_range(F)) {
     for (Instruction &I : BB) {
       if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
-        if (II->getIntrinsicID() != Intrinsic::dx_resource_handlefrombinding &&
-            II->getIntrinsicID() !=
-                Intrinsic::dx_resource_handlefromimplicitbinding)
+        if (!isResourceHandleCreation(II->getIntrinsicID()))
           continue;
         if (II->user_empty()) {
           // Initialized handle is not used anywhere.
@@ -87,9 +91,10 @@
 
   for (auto *Instr : DeadInstr) {
     if (auto *II = dyn_cast<IntrinsicInst>(Instr)) {
-      assert(II->getIntrinsicID() == Intrinsic::dx_resource_handlefrombinding ||
-             II->getIntrinsicID() ==
-                 Intrinsic::dx_resource_handlefromimplicitbinding);
+      assert(isResourceHandleCreation(II->getIntrinsicID()));
+      // A heap resource does not have an associated global variable to remove.
+      if (II->getIntrinsicID() == Intrinsic::dx_resource_handlefromheap)
+        continue;
       const unsigned ResourceNameOpIndex = 4;
       GlobalVariable *ResourceName = dyn_cast_or_null<GlobalVariable>(
           II->getArgOperand(ResourceNameOpIndex));
diff --git a/test/CodeGen/DirectX/unused-resources.ll b/test/CodeGen/DirectX/unused-resources.ll
index b11638c..ae9d565 100644
--- a/test/CodeGen/DirectX/unused-resources.ll
+++ b/test/CodeGen/DirectX/unused-resources.ll
@@ -7,6 +7,7 @@
 ; - unused resource initialization calls
 ; - unused cbuffers and associated global variables
 ; - resource name strings
+; - unused resource handles created from the descriptor heap
 
 %__cblayout_CB = type <{ i32, float }>
 %"__cblayout_$Globals" = type <{ i32, float }>
@@ -37,6 +38,10 @@
             @llvm.dx.resource.handlefrombinding(i32 0, i32 6, i32 1, i32 0, ptr @Buf.str)
   store target("dx.RawBuffer", i16, 1, 0) %uav_handle, ptr @_ZL3Buf, align 4
 
+; Heap resource
+  %heap_handle = call target("dx.RawBuffer", i16, 1, 0)
+            @llvm.dx.resource.handlefromheap(i32 0)
+
   ret void
 }
 
@@ -55,6 +60,7 @@
 ; CHECK-NOT: @Buf.str  
 ; CHECK-NOT: call {{.*}} llvm.dx.resource.handlefrombinding
 ; CHECK-NOT: call {{.*}} llvm.dx.resource.handlefromimplicitbinding
+; CHECK-NOT: call {{.*}} llvm.dx.resource.handlefromheap
 
 ; Make sure the resource bindings table is empty
 ; CHECK-PRINT: ; Resource Bindings: