Bläddra i källkod

Add resource rename pass, dx op overload fix (#2986)

- Add method to DxilModule to rename resources based on a prefix,
  preventing resource merging during linking.
- Also one to rename by binding if desired.
- Add pass to access this resource renaming.
- Fix dxil intrinsic overload names when type disambiguation causes
  types with same name to be renamed with .0+ suffix, but intrinsic
  names remain in collision.
- Handle colliding intrinsic names with unique types in linker.
Tex Riddell 5 år sedan
förälder
incheckning
e0cb412916

+ 3 - 0
include/dxc/DXIL/DxilModule.h

@@ -114,6 +114,9 @@ public:
   void RemoveResourcesWithUnusedSymbols();
   void RemoveFunction(llvm::Function *F);
 
+  bool RenameResourcesWithPrefix(const std::string &prefix);
+  bool RenameResourceGlobalsWithBinding(bool bKeepName = true);
+
   // Signatures.
   DxilSignature &GetInputSignature();
   const DxilSignature &GetInputSignature() const;

+ 3 - 0
include/dxc/DXIL/DxilOperations.h

@@ -42,6 +42,7 @@ public:
   OP(llvm::LLVMContext &Ctx, llvm::Module *pModule);
 
   void RefreshCache();
+  void FixOverloadNames();
 
   llvm::Function *GetOpFunc(OpCode OpCode, llvm::Type *pOverloadType);
   const llvm::SmallMapVector<llvm::Type *, llvm::Function *, 8> &GetOpFuncList(OpCode OpCode) const;
@@ -162,6 +163,8 @@ private:
   static unsigned GetTypeSlot(llvm::Type *pType);
   static const char *GetOverloadTypeName(unsigned TypeSlot);
   static llvm::StringRef GetTypeName(llvm::Type *Ty, std::string &str);
+  static llvm::StringRef ConstructOverloadName(llvm::Type *Ty, DXIL::OpCode opCode,
+                                               std::string &funcNameStorage);
 };
 
 } // namespace hlsl

+ 2 - 0
include/dxc/HLSL/DxilGenerationPass.h

@@ -75,6 +75,7 @@ ModulePass *createPausePassesPass();
 ModulePass *createResumePassesPass();
 FunctionPass *createMatrixBitcastLowerPass();
 ModulePass *createDxilCleanupAddrSpaceCastPass();
+ModulePass *createDxilRenameResourcesPass();
 
 void initializeDxilCondenseResourcesPass(llvm::PassRegistry&);
 void initializeDxilLowerCreateHandleForLibPass(llvm::PassRegistry&);
@@ -108,6 +109,7 @@ void initializePausePassesPass(llvm::PassRegistry&);
 void initializeResumePassesPass(llvm::PassRegistry&);
 void initializeMatrixBitcastLowerPassPass(llvm::PassRegistry&);
 void initializeDxilCleanupAddrSpaceCastPass(llvm::PassRegistry&);
+void initializeDxilRenameResourcesPass(llvm::PassRegistry&);
 
 ModulePass *createDxilValidateWaveSensitivityPass();
 void initializeDxilValidateWaveSensitivityPass(llvm::PassRegistry&);

+ 56 - 0
lib/DXIL/DxilModule.cpp

@@ -1042,6 +1042,62 @@ void DxilModule::RemoveResourcesWithUnusedSymbols() {
   RemoveResourcesWithUnusedSymbolsHelper(m_Samplers);
 }
 
+namespace {
+template <typename TResource>
+static bool RenameResources(std::vector<std::unique_ptr<TResource>> &vec, const std::string &prefix) {
+  bool bChanged = false;
+  for (auto &res : vec) {
+    res->SetGlobalName(prefix + res->GetGlobalName());
+    if (GlobalVariable *GV = dyn_cast<GlobalVariable>(res->GetGlobalSymbol())) {
+      GV->setName(prefix + GV->getName());
+    }
+    bChanged = true;
+  }
+  return bChanged;
+}
+}
+
+bool DxilModule::RenameResourcesWithPrefix(const std::string &prefix) {
+  bool bChanged = false;
+  bChanged |= RenameResources(m_SRVs, prefix);
+  bChanged |= RenameResources(m_UAVs, prefix);
+  bChanged |= RenameResources(m_CBuffers, prefix);
+  bChanged |= RenameResources(m_Samplers, prefix);
+  return bChanged;
+}
+
+namespace {
+template <typename TResource>
+static bool RenameGlobalsWithBinding(std::vector<std::unique_ptr<TResource>> &vec, llvm::StringRef prefix, bool bKeepName) {
+  bool bChanged = false;
+  for (auto &res : vec) {
+    if (res->IsAllocated()) {
+      std::string newName;
+      if (bKeepName)
+        newName = (Twine(res->GetGlobalName()) + "." + Twine(prefix) + Twine(res->GetLowerBound()) + "." + Twine(res->GetSpaceID())).str();
+      else
+        newName = (Twine(prefix) + Twine(res->GetLowerBound()) + "." + Twine(res->GetSpaceID())).str();
+
+      res->SetGlobalName(newName);
+      if (GlobalVariable *GV = dyn_cast<GlobalVariable>(res->GetGlobalSymbol())) {
+        GV->setName(newName);
+      }
+      bChanged = true;
+    }
+  }
+  return bChanged;
+}
+}
+
+bool DxilModule::RenameResourceGlobalsWithBinding(bool bKeepName) {
+  bool bChanged = false;
+  bChanged |= RenameGlobalsWithBinding(m_SRVs, "t", bKeepName);
+  bChanged |= RenameGlobalsWithBinding(m_UAVs, "u", bKeepName);
+  bChanged |= RenameGlobalsWithBinding(m_CBuffers, "b", bKeepName);
+  bChanged |= RenameGlobalsWithBinding(m_Samplers, "s", bKeepName);
+  return bChanged;
+}
+
 DxilSignature &DxilModule::GetInputSignature() {
   DXASSERT(m_DxilEntryPropsMap.size() == 1 && !m_pSM->IsLib(),
            "only works for non-lib profile");

+ 38 - 6
lib/DXIL/DxilOperations.cpp

@@ -464,6 +464,17 @@ llvm::StringRef OP::GetTypeName(Type *Ty, std::string &str) {
   }
 }
 
+llvm::StringRef OP::ConstructOverloadName(Type *Ty, DXIL::OpCode opCode,
+                                          std::string &funcNameStorage) {
+  if (Ty == Type::getVoidTy(Ty->getContext())) {
+    funcNameStorage = (Twine(OP::m_NamePrefix) + Twine(GetOpCodeClassName(opCode))).str();
+  } else {
+    funcNameStorage = (Twine(OP::m_NamePrefix) + Twine(GetOpCodeClassName(opCode)) +
+                       "." + GetTypeName(Ty, funcNameStorage)).str();
+  }
+  return funcNameStorage;
+}
+
 const char *OP::GetOpCodeName(OpCode opCode) {
   DXASSERT(0 <= (unsigned)opCode && opCode < OpCode::NumOpCodes, "otherwise caller passed OOB index");
   return m_OpCodeProps[(unsigned)opCode].pOpCodeName;
@@ -915,6 +926,30 @@ void OP::RefreshCache() {
   }
 }
 
+void OP::FixOverloadNames() {
+  // When merging code from multiple sources, such as with linking,
+  // type names that collide, but don't have the same type will be
+  // automically renamed with .0+ name disambiguation.  However,
+  // DXIL intrinsic overloads will not be renamed to disambiguate them,
+  // since they exist in separate modules at the time.
+  // This leads to name collisions between different types when linking.
+  // Do this after loading into a shared context, and before copying
+  // code into a common module, to prevent this problem.
+  for (Function &F : m_pModule->functions()) {
+    if (F.isDeclaration() && OP::IsDxilOpFunc(&F) && !F.user_empty()) {
+      CallInst *CI = cast<CallInst>(*F.user_begin());
+      DXIL::OpCode opCode = OP::GetDxilOpFuncCallInst(CI);
+      llvm::Type *Ty = OP::GetOverloadType(opCode, &F);
+      if (isa<StructType>(Ty)) {
+        std::string funcName;
+        if (OP::ConstructOverloadName(Ty, opCode, funcName).compare(F.getName()) != 0) {
+          F.setName(funcName);
+        }
+      }
+    }
+  }
+}
+
 void OP::UpdateCache(OpCodeClass opClass, Type * Ty, llvm::Function *F) {
   m_OpCodeClassCache[(unsigned)opClass].pOverloads[Ty] = F;
   m_FunctionToOpClass[F] = opClass;
@@ -955,12 +990,9 @@ Function *OP::GetOpFunc(OpCode opCode, Type *pOverloadType) {
   Type *obj = pOverloadType;
   Type *resProperty = GetResourcePropertiesType();
 
-  std::string funcName = (Twine(OP::m_NamePrefix) + Twine(GetOpCodeClassName(opCode))).str();
-  // Add ret type to the name.
-  if (pOverloadType != pV) {
-    std::string typeName;
-    funcName = Twine(funcName).concat(".").concat(GetTypeName(pOverloadType, typeName)).str();
-  } 
+  std::string funcName;
+  ConstructOverloadName(pOverloadType, opCode, funcName);
+
   // Try to find exist function with the same name in the module.
   if (Function *existF = m_pModule->getFunction(funcName)) {
     F = existF;

+ 1 - 0
lib/HLSL/CMakeLists.txt

@@ -26,6 +26,7 @@ add_llvm_library(LLVMHLSL
   DxilPatchShaderRecordBindings.cpp
   DxilNoops.cpp
   DxilPreserveAllOutputs.cpp
+  DxilRenameResourcesPass.cpp
   DxilSimpleGVNHoist.cpp
   DxilSignatureValidation.cpp
   DxilTargetLowering.cpp

+ 8 - 0
lib/HLSL/DxcOptimizer.cpp

@@ -115,6 +115,7 @@ HRESULT SetupRegistryPassForHLSL() {
     initializeDxilPromoteLocalResourcesPass(Registry);
     initializeDxilPromoteStaticResourcesPass(Registry);
     initializeDxilRemoveDeadBlocksPass(Registry);
+    initializeDxilRenameResourcesPass(Registry);
     initializeDxilRewriteOutputArgDebugInfoPass(Registry);
     initializeDxilSimpleGVNHoistPass(Registry);
     initializeDxilTranslateRawBufferPass(Registry);
@@ -209,6 +210,7 @@ static ArrayRef<LPCSTR> GetPassArgNames(LPCSTR passName) {
   static const LPCSTR DxilInsertPreservesArgs[] = { "AllowPreserves" };
   static const LPCSTR DxilOutputColorBecomesConstantArgs[] = { "mod-mode", "constant-red", "constant-green", "constant-blue", "constant-alpha" };
   static const LPCSTR DxilPIXMeshShaderOutputInstrumentationArgs[] = { "UAVSize" };
+  static const LPCSTR DxilRenameResourcesArgs[] = { "prefix", "from-binding", "keep-name" };
   static const LPCSTR DxilShaderAccessTrackingArgs[] = { "config", "checkForDynamicIndexing" };
   static const LPCSTR DynamicIndexingVectorToArrayArgs[] = { "ReplaceAllVectors" };
   static const LPCSTR Float2IntArgs[] = { "float2int-max-integer-bw" };
@@ -245,6 +247,7 @@ static ArrayRef<LPCSTR> GetPassArgNames(LPCSTR passName) {
   if (strcmp(passName, "dxil-insert-preserves") == 0) return ArrayRef<LPCSTR>(DxilInsertPreservesArgs, _countof(DxilInsertPreservesArgs));
   if (strcmp(passName, "hlsl-dxil-constantColor") == 0) return ArrayRef<LPCSTR>(DxilOutputColorBecomesConstantArgs, _countof(DxilOutputColorBecomesConstantArgs));
   if (strcmp(passName, "hlsl-dxil-pix-meshshader-output-instrumentation") == 0) return ArrayRef<LPCSTR>(DxilPIXMeshShaderOutputInstrumentationArgs, _countof(DxilPIXMeshShaderOutputInstrumentationArgs));
+  if (strcmp(passName, "dxil-rename-resources") == 0) return ArrayRef<LPCSTR>(DxilRenameResourcesArgs, _countof(DxilRenameResourcesArgs));
   if (strcmp(passName, "hlsl-dxil-pix-shader-access-instrumentation") == 0) return ArrayRef<LPCSTR>(DxilShaderAccessTrackingArgs, _countof(DxilShaderAccessTrackingArgs));
   if (strcmp(passName, "dynamic-vector-to-array") == 0) return ArrayRef<LPCSTR>(DynamicIndexingVectorToArrayArgs, _countof(DynamicIndexingVectorToArrayArgs));
   if (strcmp(passName, "float2int") == 0) return ArrayRef<LPCSTR>(Float2IntArgs, _countof(Float2IntArgs));
@@ -288,6 +291,7 @@ static ArrayRef<LPCSTR> GetPassArgDescriptions(LPCSTR passName) {
   static const LPCSTR DxilInsertPreservesArgs[] = { "None" };
   static const LPCSTR DxilOutputColorBecomesConstantArgs[] = { "None", "None", "None", "None", "None" };
   static const LPCSTR DxilPIXMeshShaderOutputInstrumentationArgs[] = { "None" };
+  static const LPCSTR DxilRenameResourcesArgs[] = { "Prefix to add to resource names", "Append binding to name when bound", "Keep name when appending binding" };
   static const LPCSTR DxilShaderAccessTrackingArgs[] = { "None", "None" };
   static const LPCSTR DynamicIndexingVectorToArrayArgs[] = { "None" };
   static const LPCSTR Float2IntArgs[] = { "Max integer bitwidth to consider in float2int" };
@@ -324,6 +328,7 @@ static ArrayRef<LPCSTR> GetPassArgDescriptions(LPCSTR passName) {
   if (strcmp(passName, "dxil-insert-preserves") == 0) return ArrayRef<LPCSTR>(DxilInsertPreservesArgs, _countof(DxilInsertPreservesArgs));
   if (strcmp(passName, "hlsl-dxil-constantColor") == 0) return ArrayRef<LPCSTR>(DxilOutputColorBecomesConstantArgs, _countof(DxilOutputColorBecomesConstantArgs));
   if (strcmp(passName, "hlsl-dxil-pix-meshshader-output-instrumentation") == 0) return ArrayRef<LPCSTR>(DxilPIXMeshShaderOutputInstrumentationArgs, _countof(DxilPIXMeshShaderOutputInstrumentationArgs));
+  if (strcmp(passName, "dxil-rename-resources") == 0) return ArrayRef<LPCSTR>(DxilRenameResourcesArgs, _countof(DxilRenameResourcesArgs));
   if (strcmp(passName, "hlsl-dxil-pix-shader-access-instrumentation") == 0) return ArrayRef<LPCSTR>(DxilShaderAccessTrackingArgs, _countof(DxilShaderAccessTrackingArgs));
   if (strcmp(passName, "dynamic-vector-to-array") == 0) return ArrayRef<LPCSTR>(DynamicIndexingVectorToArrayArgs, _countof(DynamicIndexingVectorToArrayArgs));
   if (strcmp(passName, "float2int") == 0) return ArrayRef<LPCSTR>(Float2IntArgs, _countof(Float2IntArgs));
@@ -395,7 +400,9 @@ static bool IsPassOptionName(StringRef S) {
     ||  S.equals("float2int-max-integer-bw")
     ||  S.equals("force-early-z")
     ||  S.equals("force-ssa-updater")
+    ||  S.equals("from-binding")
     ||  S.equals("jump-threading-threshold")
+    ||  S.equals("keep-name")
     ||  S.equals("likely-branch-weight")
     ||  S.equals("loop-distribute-non-if-convertible")
     ||  S.equals("loop-distribute-verify")
@@ -413,6 +420,7 @@ static bool IsPassOptionName(StringRef S) {
     ||  S.equals("parameter1")
     ||  S.equals("parameter2")
     ||  S.equals("pragma-unroll-threshold")
+    ||  S.equals("prefix")
     ||  S.equals("reroll-num-tolerated-failed-matches")
     ||  S.equals("rewrite-map-file")
     ||  S.equals("rotation-max-header-size")

+ 49 - 15
lib/HLSL/DxilLinker.cpp

@@ -128,6 +128,8 @@ public:
   void CollectUsedInitFunctions(SetVector<StringRef> &addedFunctionSet,
                                 SmallVector<StringRef, 4> &workList);
 
+  void FixIntrinsicOverloads();
+
 private:
   std::unique_ptr<llvm::Module> m_pModule;
   DxilModule &m_DM;
@@ -188,7 +190,7 @@ DxilFunctionLinkInfo::DxilFunctionLinkInfo(Function *F) : func(F) {
 DxilLib::DxilLib(std::unique_ptr<llvm::Module> pModule)
     : m_pModule(std::move(pModule)), m_DM(m_pModule->GetOrCreateDxilModule()) {
   Module &M = *m_pModule;
-  const std::string &MID = M.getModuleIdentifier();
+  const std::string MID = (Twine(M.getModuleIdentifier()) + ".").str();
 
   // Collect function defines.
   for (Function &F : M.functions()) {
@@ -211,6 +213,14 @@ DxilLib::DxilLib(std::unique_ptr<llvm::Module> pModule)
   }
 }
 
+void DxilLib::FixIntrinsicOverloads() {
+  // Fix DXIL overload name collisions that may be caused by name
+  // collisions between dxil ops with different overload types,
+  // when those types may have had the same name in the original
+  // modules.
+  m_DM.GetOP()->FixOverloadNames();
+}
+
 void DxilLib::LazyLoadFunction(Function *F) {
   DXASSERT(m_functionNameMap.count(F->getName()), "else invalid Function");
   DxilFunctionLinkInfo *linkInfo = m_functionNameMap[F->getName()].get();
@@ -354,14 +364,24 @@ private:
   bool AddResource(DxilResourceBase *res, llvm::GlobalVariable *GV);
   void AddResourceToDM(DxilModule &DM);
   llvm::MapVector<DxilFunctionLinkInfo *, DxilLib *> m_functionDefs;
-  llvm::StringMap<llvm::Function *> m_functionDecls;
-  // New created functions.
-  llvm::StringMap<llvm::Function *> m_newFunctions;
-  // New created globals.
-  llvm::StringMap<llvm::GlobalVariable *> m_newGlobals;
-  // Map for resource.
-  llvm::StringMap<std::pair<DxilResourceBase *, llvm::GlobalVariable *>>
-      m_resourceMap;
+
+  // Function decls, in order added.
+  llvm::MapVector<llvm::StringRef,
+                  std::pair<llvm::SmallPtrSet<llvm::FunctionType *, 2>,
+                            llvm::SmallVector<llvm::Function *, 2>>>
+    m_functionDecls;
+
+  // New created functions, in order added.
+  llvm::MapVector<llvm::StringRef, llvm::Function *> m_newFunctions;
+
+  // New created globals, in order added.
+  llvm::MapVector<llvm::StringRef, llvm::GlobalVariable *> m_newGlobals;
+
+  // Map for resource, ordered by name.
+  std::map<llvm::StringRef,
+           std::pair<DxilResourceBase *, llvm::GlobalVariable *>>
+    m_resourceMap;
+
   LLVMContext &m_ctx;
   dxilutil::ExportMap &m_exportMap;
   unsigned m_valMajor, m_valMinor;
@@ -575,11 +595,15 @@ void DxilLinkJob::LinkNamedMDNodes(Module *pM, ValueToValueMapTy &vmap) {
 
 void DxilLinkJob::AddFunctionDecls(Module *pM) {
   for (auto &it : m_functionDecls) {
-    Function *F = it.second;
-    Function *NewF = Function::Create(F->getFunctionType(), F->getLinkage(),
-                                      F->getName(), pM);
-    NewF->setAttributes(F->getAttributes());
-    m_newFunctions[NewF->getName()] = NewF;
+    for (auto F : it.second.second) {
+      Function *NewF = pM->getFunction(F->getName());
+      if (!NewF || F->getFunctionType() != NewF->getFunctionType()) {
+        NewF = Function::Create(F->getFunctionType(), F->getLinkage(),
+                                          F->getName(), pM);
+        NewF->setAttributes(F->getAttributes());
+      }
+      m_newFunctions[F->getName()] = NewF;
+    }
   }
 }
 
@@ -934,7 +958,12 @@ void DxilLinkJob::AddFunction(
 }
 
 void DxilLinkJob::AddFunction(llvm::Function *F) {
-  m_functionDecls[F->getName()] = F;
+  // Rarely, DXIL op overloads could collide, due to different types with same name.
+  // Later, we will rename these functions, but for now, we need to prevent clobbering
+  // an existing entry.
+  auto &entry = m_functionDecls[F->getName()];
+  if (entry.first.insert(F->getFunctionType()).second)
+    entry.second.push_back(F);
 }
 
 // Clone of StripDeadDebugInfo::runOnModule.
@@ -1315,6 +1344,11 @@ DxilLinkerImpl::Link(StringRef entry, StringRef profile, dxilutil::ExportMap &ex
   for (auto &pLib : libSet) {
     pLib->CollectUsedInitFunctions(addedFunctionSet, workList);
   }
+
+  for (auto &pLib : libSet) {
+    pLib->FixIntrinsicOverloads();
+  }
+
   // Add init functions if used.
   // All init function already loaded in BuildGlobalUsage,
   // so set bLazyLoadDone to true here.

+ 75 - 0
lib/HLSL/DxilRenameResourcesPass.cpp

@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////
+//                                                                           //
+// DxilRenameResourcesPass.cpp                                             //
+// Copyright (C) Microsoft Corporation. All rights reserved.                 //
+// This file is distributed under the University of Illinois Open Source     //
+// License. See LICENSE.TXT for details.                                     //
+//                                                                           //
+///////////////////////////////////////////////////////////////////////////////
+
+#include "llvm/Pass.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Instruction.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "dxc/DXIL/DxilModule.h"
+#include "dxc/HLSL/DxilGenerationPass.h"
+
+using namespace llvm;
+using namespace hlsl;
+
+// Rename resources with prefix
+
+namespace {
+
+class DxilRenameResources : public ModulePass {
+public:
+  static char ID; // Pass identification, replacement for typeid
+  explicit DxilRenameResources()
+      : ModulePass(ID) {}
+
+  void applyOptions(PassOptions O) override {
+    GetPassOptionBool(O, "from-binding", &m_bFromBinding, false);
+    GetPassOptionBool(O, "keep-name", &m_bKeepName, true);
+    StringRef prefix;
+    GetPassOption(O, "prefix", &prefix);
+    m_Prefix = prefix.str();
+  }
+
+  const char *getPassName() const override {
+    return "DXIL rename resources";
+  }
+
+  bool runOnModule(Module &M) override {
+    DxilModule &DM = M.GetOrCreateDxilModule();
+    bool bChanged = false;
+    if (m_bFromBinding) {
+      bChanged = DM.RenameResourceGlobalsWithBinding(m_bKeepName);
+    }
+    if (!m_Prefix.empty()) {
+      bChanged |= DM.RenameResourcesWithPrefix(m_Prefix);
+    }
+    if (bChanged) {
+      DM.ReEmitDxilResources();
+    }
+    return bChanged;
+  }
+
+private:
+  bool m_bFromBinding;
+  bool m_bKeepName;
+  std::string m_Prefix;
+};
+
+char DxilRenameResources::ID = 0;
+
+}
+
+ModulePass *llvm::createDxilRenameResourcesPass() {
+  return new DxilRenameResources();
+}
+
+INITIALIZE_PASS(DxilRenameResources,
+                "dxil-rename-resources",
+                "DXIL rename resources", false, false)

+ 15 - 0
tools/clang/test/CodeGenHLSL/lib_res_bound1.hlsl

@@ -0,0 +1,15 @@
+struct S
+{
+    float values;
+};
+
+ConstantBuffer<S> g_buf : register(b0);
+RWByteAddressBuffer b : register(u0);
+
+float Extern(uint dtid);
+
+[numthreads(31, 1, 1)]
+void main(uint dtid : SV_DispatchThreadId)
+{
+    b.Store2(dtid * 4, float2(Extern(dtid), g_buf.values));
+}

+ 12 - 0
tools/clang/test/CodeGenHLSL/lib_res_bound2.hlsl

@@ -0,0 +1,12 @@
+struct S1
+{
+    float values;
+};
+
+ConstantBuffer<S1> g_buf : register(b1);
+
+export
+float Extern(uint dtid)
+{
+     return g_buf.values + dtid;
+}

+ 56 - 1
tools/clang/unittests/HLSL/LinkerTest.cpp

@@ -39,6 +39,7 @@ public:
   TEST_CLASS_SETUP(InitSupport);
 
   TEST_METHOD(RunLinkResource);
+  TEST_METHOD(RunLinkResourceWithBinding);
   TEST_METHOD(RunLinkAllProfiles);
   TEST_METHOD(RunLinkFailNoDefine);
   TEST_METHOD(RunLinkFailReDefine);
@@ -180,6 +181,61 @@ TEST_F(LinkerTest, RunLinkResource) {
   Link(L"entry", L"cs_6_0", pLinker, {libResName, libName}, {} ,{});
 }
 
+TEST_F(LinkerTest, RunLinkResourceWithBinding) {
+  // These two libraries both have a ConstantBuffer resource named g_buf.
+  // These are explicitly bound to different slots, and the types don't match.
+  // This test runs a pass to rename resources to prevent merging of resource globals.
+  // Then tests linking these, which requires dxil op overload renaming
+  // because of a typename collision between the two libraries.
+  CComPtr<IDxcBlob> pLib1;
+  CompileLib(L"..\\CodeGenHLSL\\lib_res_bound1.hlsl", &pLib1);
+  CComPtr<IDxcBlob> pLib2;
+  CompileLib(L"..\\CodeGenHLSL\\lib_res_bound2.hlsl", &pLib2);
+
+  LPCWSTR optOptions[] = {
+    L"-dxil-rename-resources,prefix=lib1",
+    L"-dxil-rename-resources,prefix=lib2",
+  };
+
+  CComPtr<IDxcOptimizer> pOptimizer;
+  VERIFY_SUCCEEDED(m_dllSupport.CreateInstance(CLSID_DxcOptimizer, &pOptimizer));
+
+  CComPtr<IDxcContainerReflection> pContainerReflection;
+  VERIFY_SUCCEEDED(m_dllSupport.CreateInstance(CLSID_DxcContainerReflection, &pContainerReflection));
+  UINT32 partIdx = 0;
+  VERIFY_SUCCEEDED(pContainerReflection->Load(pLib1));
+  VERIFY_SUCCEEDED(pContainerReflection->FindFirstPartKind(DXC_PART_DXIL, &partIdx));
+  CComPtr<IDxcBlob> pLib1Module;
+  VERIFY_SUCCEEDED(pContainerReflection->GetPartContent(partIdx, &pLib1Module));
+
+  CComPtr<IDxcBlob> pLib1ModuleRenamed;
+  VERIFY_SUCCEEDED(pOptimizer->RunOptimizer(pLib1Module, &optOptions[0], 1, &pLib1ModuleRenamed, nullptr));
+  pLib1Module.Release();
+  pLib1.Release();
+  AssembleToContainer(m_dllSupport, pLib1ModuleRenamed, &pLib1);
+
+  VERIFY_SUCCEEDED(pContainerReflection->Load(pLib2));
+  VERIFY_SUCCEEDED(pContainerReflection->FindFirstPartKind(DXC_PART_DXIL, &partIdx));
+  CComPtr<IDxcBlob> pLib2Module;
+  VERIFY_SUCCEEDED(pContainerReflection->GetPartContent(partIdx, &pLib2Module));
+
+  CComPtr<IDxcBlob> pLib2ModuleRenamed;
+  VERIFY_SUCCEEDED(pOptimizer->RunOptimizer(pLib2Module, &optOptions[1], 1, &pLib2ModuleRenamed, nullptr));
+  pLib2Module.Release();
+  pLib2.Release();
+  AssembleToContainer(m_dllSupport, pLib2ModuleRenamed, &pLib2);
+
+  CComPtr<IDxcLinker> pLinker;
+  CreateLinker(&pLinker);
+  LPCWSTR lib1Name = L"lib1";
+  RegisterDxcModule(lib1Name, pLib1, pLinker);
+
+  LPCWSTR lib2Name = L"lib2";
+  RegisterDxcModule(lib2Name, pLib2, pLinker);
+
+  Link(L"main", L"cs_6_0", pLinker, {lib1Name, lib2Name}, {} ,{});
+}
+
 TEST_F(LinkerTest, RunLinkAllProfiles) {
   CComPtr<IDxcLinker> pLinker;
   CreateLinker(&pLinker);
@@ -258,7 +314,6 @@ TEST_F(LinkerTest, RunLinkFailReDefineGlobal) {
   CComPtr<IDxcBlob> pLib1;
   CompileLib(L"..\\CodeGenHLSL\\lib_global4.hlsl", &pLib1);
 
-
   CComPtr<IDxcLinker> pLinker;
   CreateLinker(&pLinker);
 

+ 5 - 0
utils/hct/hctdb.py

@@ -2106,6 +2106,11 @@ class db_dxil(object):
         add_pass('dxil-delete-loop', 'DxilLoopDeletion', 'Dxil Loop Deletion', [])
         add_pass('dxil-value-cache', 'DxilValueCache', 'Dxil Value Cache',[])
         add_pass('hlsl-cleanup-dxbreak', 'CleanupDxBreak', 'HLSL Remove unnecessary dx.break conditions', [])
+        add_pass('dxil-rename-resources', 'DxilRenameResources', 'Rename resources to prevent merge by name during linking', [
+                {'n':'prefix', 'i':'Prefix', 't':'string', 'd':'Prefix to add to resource names'},
+                {'n':'from-binding', 'i':'FromBinding', 't':'bool', 'c':1, 'd':'Append binding to name when bound'},
+                {'n':'keep-name', 'i':'KeepName', 't':'bool', 'c':1, 'd':'Keep name when appending binding'},
+            ])
 
         category_lib="llvm"
         add_pass('ipsccp', 'IPSCCP', 'Interprocedural Sparse Conditional Constant Propagation', [])