diff --git a/lib/DxilPIXPasses/DxilDebugBreakInstrumentation.cpp b/lib/DxilPIXPasses/DxilDebugBreakInstrumentation.cpp index 30799c4bdc..13cbf7c6c8 100644 --- a/lib/DxilPIXPasses/DxilDebugBreakInstrumentation.cpp +++ b/lib/DxilPIXPasses/DxilDebugBreakInstrumentation.cpp @@ -120,10 +120,8 @@ bool DxilDebugBreakInstrumentation::runOnModule(Module &M) { CI->eraseFromParent(); } - // Clean up the now-unused declaration. Not strictly required for - // correctness, but keeps the module free of dead references. - if (DebugBreakFunc->use_empty()) - DebugBreakFunc->eraseFromParent(); + PIXPassHelpers::eraseIfUnused(DM, DebugBreakFunc); + PIXPassHelpers::eraseIfUnused(DM, AtomicOpFunc); const bool modified = (PixUAVResource != nullptr); diff --git a/lib/DxilPIXPasses/DxilDebugInstrumentation.cpp b/lib/DxilPIXPasses/DxilDebugInstrumentation.cpp index 4dd43b07cc..a40acfe860 100644 --- a/lib/DxilPIXPasses/DxilDebugInstrumentation.cpp +++ b/lib/DxilPIXPasses/DxilDebugInstrumentation.cpp @@ -1436,23 +1436,6 @@ bool DxilDebugInstrumentation::RunOnFunction(Module &M, DxilModule &DM, auto &values = m_FunctionToValues[BC.Builder.GetInsertBlock()->getParent()]; - // PIX binds two UAVs when running this instrumentation: one for raygen - // shaders and another for the hitgroups and miss shaders. Since PIX invokes - // this pass at the library level, which may contain examples of both types, - // PIX can't really specify which UAV index to use per-shader. This pass - // therefore just has to know this: - constexpr unsigned int RayGenUAVRegister = 0; - constexpr unsigned int HitGroupAndMissUAVRegister = 1; - unsigned int UAVRegisterId = RayGenUAVRegister; - switch (shaderKind) { - case DXIL::ShaderKind::ClosestHit: - case DXIL::ShaderKind::Intersection: - case DXIL::ShaderKind::AnyHit: - case DXIL::ShaderKind::Miss: - UAVRegisterId = HitGroupAndMissUAVRegister; - break; - } - values.UAVHandle = PIXPassHelpers::CreateHandleForResource( DM, Builder, uav, "PIX_DebugUAV_Handle"); diff --git a/lib/DxilPIXPasses/DxilNonUniformResourceIndexInstrumentation.cpp b/lib/DxilPIXPasses/DxilNonUniformResourceIndexInstrumentation.cpp index a442bfabed..cbeb778c41 100644 --- a/lib/DxilPIXPasses/DxilNonUniformResourceIndexInstrumentation.cpp +++ b/lib/DxilPIXPasses/DxilNonUniformResourceIndexInstrumentation.cpp @@ -149,6 +149,9 @@ bool DxilNonUniformResourceIndexInstrumentation::runOnModule(Module &M) { const bool modified = (PixUAVResource != nullptr); + PIXPassHelpers::eraseIfUnused(DM, WaveActiveAllEqualFunc); + PIXPassHelpers::eraseIfUnused(DM, AtomicOpFunc); + if (modified) { DM.ReEmitDxilResources(); diff --git a/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp b/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp index a0749fbe0f..ff389f9348 100644 --- a/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp +++ b/lib/DxilPIXPasses/DxilOutputColorBecomesConstant.cpp @@ -125,6 +125,8 @@ bool DxilOutputColorBecomesConstant::runOnModule(Module &M) { [&hasIntOutputs](CallInst *) { hasIntOutputs = true; }); if (!hasFloatOutputs && !hasIntOutputs) { + PIXPassHelpers::eraseIfUnused(DM, FloatOutputFunction); + PIXPassHelpers::eraseIfUnused(DM, IntOutputFunction); return false; } @@ -251,6 +253,9 @@ bool DxilOutputColorBecomesConstant::runOnModule(Module &M) { }); } + PIXPassHelpers::eraseIfUnused(DM, FloatOutputFunction); + PIXPassHelpers::eraseIfUnused(DM, IntOutputFunction); + return Modified; } diff --git a/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp b/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp index e756e98781..10122f748f 100644 --- a/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp +++ b/lib/DxilPIXPasses/DxilPIXAddTidToAmplificationShaderPayload.cpp @@ -183,8 +183,11 @@ bool DxilPIXAddTidToAmplificationShaderPayload::runOnModule(Module &M) { {DispatchMeshOpcode, DispatchMesh.get_threadGroupCountX(), DispatchMesh.get_threadGroupCountY(), DispatchMesh.get_threadGroupCountZ(), NewStructAlloca}); + llvm::Function *OriginalDispatchMeshFn = + cast(&*I)->getCalledFunction(); I->removeFromParent(); delete &*I; + PIXPassHelpers::eraseIfUnused(DM, OriginalDispatchMeshFn); // Validation requires exactly one DispatchMesh in an AS, so we can exit // after the first one: DM.ReEmitDxilResources(); diff --git a/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp b/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp index 507a718a4c..de04a246e1 100644 --- a/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp +++ b/lib/DxilPIXPasses/DxilPIXMeshShaderOutputInstrumentation.cpp @@ -310,6 +310,8 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { } if (getMeshPayloadInstructions != nullptr) { + llvm::Function *OriginalGetMeshPayloadFunction = + cast(getMeshPayloadInstructions)->getCalledFunction(); Function *DxilFunc = HlslOP->GetOpFunc( OP::OpCode::GetMeshPayload, expanded.ExpandedPayloadStructPtrType); @@ -326,6 +328,7 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { ReplaceAllUsesOfInstructionWithNewValueAndDeleteInstruction( getMeshPayloadInstructions, payload, expanded.ExpandedPayloadStructType); + PIXPassHelpers::eraseIfUnused(DM, OriginalGetMeshPayloadFunction); } } @@ -378,9 +381,11 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { {Type::getInt16Ty(Ctx), int16ValueIndicator}, {Type::getFloatTy(Ctx), floatValueIndicator}, {Type::getHalfTy(Ctx), float16ValueIndicator}}; + SmallVector StoreVertexOutputFunctions; for (auto const &Overload : StoreVertexOutputOverloads) { F = HlslOP->GetOpFunc(DXIL::OpCode::StoreVertexOutput, Overload.type); + StoreVertexOutputFunctions.push_back(F); FunctionUses = F->uses(); for (auto FI = FunctionUses.begin(); FI != FunctionUses.end();) { auto &FunctionUse = *FI++; @@ -419,6 +424,10 @@ bool DxilPIXMeshShaderOutputInstrumentation::runOnModule(Module &M) { } } + for (Function *StoreVertexOutputFunction : StoreVertexOutputFunctions) { + PIXPassHelpers::eraseIfUnused(DM, StoreVertexOutputFunction); + } + DM.ReEmitDxilResources(); return true; diff --git a/lib/DxilPIXPasses/DxilRemoveDiscards.cpp b/lib/DxilPIXPasses/DxilRemoveDiscards.cpp index 5f8d9263f0..492dfebdb4 100644 --- a/lib/DxilPIXPasses/DxilRemoveDiscards.cpp +++ b/lib/DxilPIXPasses/DxilRemoveDiscards.cpp @@ -17,6 +17,8 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/PassManager.h" +#include "PixPassHelpers.h" + using namespace llvm; using namespace hlsl; @@ -53,6 +55,8 @@ bool DxilRemoveDiscards::runOnModule(Module &M) { Modified = true; } + PIXPassHelpers::eraseIfUnused(DM, DiscardFunction); + return Modified; } diff --git a/lib/DxilPIXPasses/PixPassHelpers.cpp b/lib/DxilPIXPasses/PixPassHelpers.cpp index c7c99cf763..c16d8a2fa3 100644 --- a/lib/DxilPIXPasses/PixPassHelpers.cpp +++ b/lib/DxilPIXPasses/PixPassHelpers.cpp @@ -185,6 +185,9 @@ static std::vector SerializeRootSignatureToVector( SerializeRootSignature(rootSignature, &serializedRootSignature, &errorBlob, allowReservedRegisterSpace); std::vector ret; + if (serializedRootSignature == nullptr) { + return ret; + } auto const *serializedData = reinterpret_cast( serializedRootSignature->GetBufferPointer()); ret.assign(serializedData, @@ -194,69 +197,84 @@ static std::vector SerializeRootSignatureToVector( } constexpr uint32_t toolsRegisterSpace = static_cast(-2); -constexpr uint32_t toolsUAVRegister = 0; +// Returns whether a parameter was appended. template -void ExtendRootSig(RootSigDesc &rootSigDesc) { - auto *existingParams = rootSigDesc.pParameters; - for (uint32_t i = 0; i < rootSigDesc.NumParameters; ++i) { - if (rootSigDesc.pParameters[i].ParameterType == +bool ExtendRootSig(RootSigDesc &RootSignatureDesc, uint32_t ToolsUAVRegister) { + auto *existingParams = RootSignatureDesc.pParameters; + for (uint32_t i = 0; i < RootSignatureDesc.NumParameters; ++i) { + if (RootSignatureDesc.pParameters[i].ParameterType == DxilRootParameterType::UAV) { - if (rootSigDesc.pParameters[i].Descriptor.RegisterSpace == + if (RootSignatureDesc.pParameters[i].Descriptor.RegisterSpace == toolsRegisterSpace && - rootSigDesc.pParameters[i].Descriptor.ShaderRegister == - toolsUAVRegister) { + RootSignatureDesc.pParameters[i].Descriptor.ShaderRegister == + ToolsUAVRegister) { // Already added - return; + return false; } } } - auto *newParams = new RootParameterDesc[rootSigDesc.NumParameters + 1]; + auto *newParams = new RootParameterDesc[RootSignatureDesc.NumParameters + 1]; if (existingParams != nullptr) { memcpy(newParams, existingParams, - rootSigDesc.NumParameters * sizeof(RootParameterDesc)); + RootSignatureDesc.NumParameters * sizeof(RootParameterDesc)); delete[] existingParams; } - rootSigDesc.pParameters = newParams; - rootSigDesc.pParameters[rootSigDesc.NumParameters].ParameterType = + RootSignatureDesc.pParameters = newParams; + RootSignatureDesc.pParameters[RootSignatureDesc.NumParameters].ParameterType = DxilRootParameterType::UAV; - rootSigDesc.pParameters[rootSigDesc.NumParameters].Descriptor.RegisterSpace = - toolsRegisterSpace; - rootSigDesc.pParameters[rootSigDesc.NumParameters].Descriptor.ShaderRegister = - toolsUAVRegister; - rootSigDesc.pParameters[rootSigDesc.NumParameters].ShaderVisibility = - DxilShaderVisibility::All; - rootSigDesc.NumParameters++; + RootSignatureDesc.pParameters[RootSignatureDesc.NumParameters] + .Descriptor.RegisterSpace = toolsRegisterSpace; + RootSignatureDesc.pParameters[RootSignatureDesc.NumParameters] + .Descriptor.ShaderRegister = ToolsUAVRegister; + RootSignatureDesc.pParameters[RootSignatureDesc.NumParameters] + .ShaderVisibility = DxilShaderVisibility::All; + RootSignatureDesc.NumParameters++; + return true; } -static std::vector AddUAVParamterToRootSignature(const void *Data, - uint32_t Size) { +static std::vector +AddUAVParamterToRootSignature(const void *Data, uint32_t Size, + uint32_t ToolsUAVRegister) { DxilVersionedRootSignature rootSignature; DeserializeRootSignature(Data, Size, rootSignature.get_address_of()); auto *rs = rootSignature.get_mutable(); switch (rootSignature->Version) { case DxilRootSignatureVersion::Version_1_0: - ExtendRootSig(rs->Desc_1_0); + ExtendRootSig(rs->Desc_1_0, + ToolsUAVRegister); break; case DxilRootSignatureVersion::Version_1_1: - ExtendRootSig(rs->Desc_1_1); - rs->Desc_1_1.pParameters[rs->Desc_1_1.NumParameters - 1].Descriptor.Flags = - hlsl::DxilRootDescriptorFlags::None; + if (ExtendRootSig( + rs->Desc_1_1, ToolsUAVRegister)) { + rs->Desc_1_1.pParameters[rs->Desc_1_1.NumParameters - 1] + .Descriptor.Flags = hlsl::DxilRootDescriptorFlags::None; + } break; } return SerializeRootSignatureToVector(rs); } -static void AddUAVToShaderAttributeRootSignature(DxilModule &DM) { +static void AddUAVToShaderAttributeRootSignature(DxilModule &DM, + uint32_t ToolsUAVRegister) { auto rs = DM.GetSerializedRootSignature(); if (!rs.empty()) { std::vector asVector = AddUAVParamterToRootSignature( - rs.data(), static_cast(rs.size())); - DM.ResetSerializedRootSignature(asVector); + rs.data(), static_cast(rs.size()), ToolsUAVRegister); + if (!asVector.empty()) { + DM.ResetSerializedRootSignature(asVector); + } } } -static void AddUAVToDxilDefinedGlobalRootSignatures(DxilModule &DM) { +static void AddUAVToDxilDefinedGlobalRootSignatures(DxilModule &DM, + uint32_t ToolsUAVRegister) { + struct ReplacementRootSignature { + std::string Name; + std::vector Data; + }; + + std::vector ReplacementRootSignatures; auto *subObjects = DM.GetSubobjects(); if (subObjects != nullptr) { for (auto const &subObject : subObjects->GetSubobjects()) { @@ -267,16 +285,24 @@ static void AddUAVToDxilDefinedGlobalRootSignatures(DxilModule &DM) { constexpr bool notALocalRS = false; if (subObject.second->GetRootSignature(notALocalRS, Data, Size, nullptr)) { - auto extendedRootSig = AddUAVParamterToRootSignature(Data, Size); - auto rootSignatureSubObjectName = subObject.first; - subObjects->RemoveSubobject(rootSignatureSubObjectName); - subObjects->CreateRootSignature( - rootSignatureSubObjectName, notALocalRS, extendedRootSig.data(), - static_cast(extendedRootSig.size())); - break; + std::vector ExtendedRootSignature = + AddUAVParamterToRootSignature(Data, Size, ToolsUAVRegister); + if (!ExtendedRootSignature.empty()) { + ReplacementRootSignatures.push_back( + {subObject.first.str(), std::move(ExtendedRootSignature)}); + } } } } + + constexpr bool NotALocalRootSignature = false; + for (const ReplacementRootSignature &Replacement : + ReplacementRootSignatures) { + subObjects->RemoveSubobject(Replacement.Name); + subObjects->CreateRootSignature( + Replacement.Name, NotALocalRootSignature, Replacement.Data.data(), + static_cast(Replacement.Data.size())); + } } } @@ -286,6 +312,13 @@ hlsl::DxilResource *CreateGlobalUAVResource(hlsl::DxilModule &DM, const char *name) { LLVMContext &Ctx = DM.GetModule()->getContext(); + for (const std::unique_ptr &ExistingUAV : DM.GetUAVs()) { + if (ExistingUAV->GetSpaceID() == toolsRegisterSpace && + ExistingUAV->GetLowerBound() == hlslBindIndex) { + return ExistingUAV.get(); + } + } + const char *PIXStructTypeName = ShaderModelHandleTypeName(DM); llvm::StructType *UAVStructTy = DM.GetModule()->getTypeByName(PIXStructTypeName); @@ -295,10 +328,8 @@ hlsl::DxilResource *CreateGlobalUAVResource(hlsl::DxilModule &DM, UAVStructTy = llvm::StructType::create(Elements, PIXStructTypeName); } - // Since this function should only be called once per module, - // we can modify the root sig at the same time: - AddUAVToDxilDefinedGlobalRootSignatures(DM); - AddUAVToShaderAttributeRootSignature(DM); + AddUAVToDxilDefinedGlobalRootSignatures(DM, hlslBindIndex); + AddUAVToShaderAttributeRootSignature(DM, hlslBindIndex); unsigned int Id = static_cast(DM.GetUAVs().size()); std::unique_ptr pUAV = llvm::make_unique(); @@ -320,8 +351,7 @@ hlsl::DxilResource *CreateGlobalUAVResource(hlsl::DxilModule &DM, } pUAV->SetGlobalName(name); pUAV->SetRW(true); // sets UAV class - pUAV->SetSpaceID( - (unsigned int)-2); // This is the reserved-for-tools register space + pUAV->SetSpaceID(toolsRegisterSpace); // reserved-for-tools register space pUAV->SetSampleCount(0); // This is what compiler generates for a raw UAV pUAV->SetGloballyCoherent(false); pUAV->SetReorderCoherent(false); @@ -351,9 +381,17 @@ hlsl::DxilResource *CreateGlobalUAVResource(hlsl::DxilModule &DM, auto *ret = pUAV.get(); DM.AddUAV(std::move(pUAV)); + DM.CollectShaderFlagsForModule(); return ret; } +void eraseIfUnused(hlsl::DxilModule &DM, llvm::Function *OpFunction) { + if (OpFunction != nullptr && OpFunction->user_empty()) { + DM.GetOP()->RemoveFunction(OpFunction); + OpFunction->eraseFromParent(); + } +} + // Set up a UAV with structure of a single int llvm::CallInst *CreateUAVOnceForModule(hlsl::DxilModule &DM, llvm::IRBuilder<> &Builder, @@ -401,18 +439,6 @@ hlsl::DXIL::ShaderKind GetFunctionShaderKind(hlsl::DxilModule &DM, return shaderKind; } -std::vector GetAllBlocks(hlsl::DxilModule &DM) { - std::vector ret; - auto entryPoints = DM.GetExportedFunctions(); - for (auto &fn : entryPoints) { - auto &blocks = fn->getBasicBlockList(); - for (auto &block : blocks) { - ret.push_back(&block); - } - } - return ret; -} - ExpandedStruct ExpandStructType(LLVMContext &Ctx, Type *OriginalPayloadStructType) { SmallVector Elements; @@ -547,6 +573,24 @@ void ForEachDynamicallyIndexedResource( auto CreateHandleFn = HlslOP->GetOpFunc(DXIL::OpCode::CreateHandle, Type::getVoidTy(Ctx)); + llvm::Function *CreateHandleFromBindingFn = HlslOP->GetOpFunc( + DXIL::OpCode::CreateHandleFromBinding, Type::getVoidTy(Ctx)); + llvm::Function *CreateHandleFromHeapFn = HlslOP->GetOpFunc( + DXIL::OpCode::CreateHandleFromHeap, Type::getVoidTy(Ctx)); + + struct UnusedDeclarationCleanup { + hlsl::DxilModule &DM; + llvm::Function *CreateHandleFn; + llvm::Function *CreateHandleFromBindingFn; + llvm::Function *CreateHandleFromHeapFn; + ~UnusedDeclarationCleanup() { + eraseIfUnused(DM, CreateHandleFn); + eraseIfUnused(DM, CreateHandleFromBindingFn); + eraseIfUnused(DM, CreateHandleFromHeapFn); + } + } Cleanup{DM, CreateHandleFn, CreateHandleFromBindingFn, + CreateHandleFromHeapFn}; + for (auto FI = CreateHandleFn->user_begin(); FI != CreateHandleFn->user_end();) { auto *FunctionUser = *FI++; @@ -562,8 +606,6 @@ void ForEachDynamicallyIndexedResource( } } - auto CreateHandleFromBindingFn = HlslOP->GetOpFunc( - DXIL::OpCode::CreateHandleFromBinding, Type::getVoidTy(Ctx)); for (auto FI = CreateHandleFromBindingFn->user_begin(); FI != CreateHandleFromBindingFn->user_end();) { auto *FunctionUser = *FI++; @@ -579,8 +621,6 @@ void ForEachDynamicallyIndexedResource( } } - auto CreateHandleFromHeapFn = HlslOP->GetOpFunc( - DXIL::OpCode::CreateHandleFromHeap, Type::getVoidTy(Ctx)); for (auto FI = CreateHandleFromHeapFn->user_begin(); FI != CreateHandleFromHeapFn->user_end();) { auto *FunctionUser = *FI++; diff --git a/lib/DxilPIXPasses/PixPassHelpers.h b/lib/DxilPIXPasses/PixPassHelpers.h index d7b0b40af8..5efdc4940c 100644 --- a/lib/DxilPIXPasses/PixPassHelpers.h +++ b/lib/DxilPIXPasses/PixPassHelpers.h @@ -48,7 +48,7 @@ llvm::CallInst *CreateHandleForResource(hlsl::DxilModule &DM, hlsl::DxilResourceBase *resource, const char *name); llvm::Function *GetEntryFunction(hlsl::DxilModule &DM); -std::vector GetAllBlocks(hlsl::DxilModule &DM); +void eraseIfUnused(hlsl::DxilModule &DM, llvm::Function *OpFunction); std::vector GetAllInstrumentableFunctions(hlsl::DxilModule &DM); hlsl::DXIL::ShaderKind GetFunctionShaderKind(hlsl::DxilModule &DM, diff --git a/tools/clang/test/HLSLFileCheck/pix/pixelCounterEarlyZ.hlsl b/tools/clang/test/HLSLFileCheck/pix/pixelCounterEarlyZ.hlsl index 86d6d498b8..63968a3375 100644 --- a/tools/clang/test/HLSLFileCheck/pix/pixelCounterEarlyZ.hlsl +++ b/tools/clang/test/HLSLFileCheck/pix/pixelCounterEarlyZ.hlsl @@ -3,9 +3,11 @@ // Check the write to the UAV was emitted: // CHECK: %UAVIncResult = call i32 @dx.op.atomicBinOp.i32(i32 78, %dx.types.Handle %PIX_CountUAV_Handle, i32 0, i32 %ByteIndex, i32 undef, i32 undef, i32 1) -// Early z flag value is 8. The flags are stored in an entry in the entry function description record. See: +// The flags are stored in an entry in the entry function description record. See: // https://github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/DXIL.rst#shader-properties-and-capabilities -// CHECK: !{i32 0, i64 8} +// 8 is force-early-z. 16 is EnableRawAndStructuredBuffers, set for the +// RWByteAddressBuffer counter this pass adds. 8 | 16 = 24. +// CHECK: !{i32 0, i64 24} float4 main(float4 pos : SV_Position) : SV_Target { return pos; diff --git a/tools/clang/unittests/HLSL/PixTest.cpp b/tools/clang/unittests/HLSL/PixTest.cpp index 9d9131de5d..0d8ce95081 100644 --- a/tools/clang/unittests/HLSL/PixTest.cpp +++ b/tools/clang/unittests/HLSL/PixTest.cpp @@ -33,7 +33,10 @@ #include #endif +#include "dxc/DXIL/DxilConstants.h" #include "dxc/DXIL/DxilModule.h" +#include "dxc/DXIL/DxilOperations.h" +#include "dxc/DXIL/DxilSubobject.h" #include "dxc/Test/DxcTestUtils.h" #include "dxc/Test/HLSLTestData.h" @@ -66,6 +69,7 @@ #include #include <../lib/DxilDia/DxcPixLiveVariables_FragmentIterator.h> +#include <../lib/DxilPIXPasses/PixPassHelpers.h> #include #include "PixTestUtils.h" @@ -144,6 +148,17 @@ class PixTest : public ::testing::Test { TEST_METHOD(RootSignatureUpgrade_SubObjects) TEST_METHOD(RootSignatureUpgrade_Annotation) + TEST_METHOD(ToolsUav_TwoPixPassesShareOneResource) + TEST_METHOD(ToolsUav_LibraryWithTwoEntryPointsCreatesOnePair) + TEST_METHOD(ToolsUav_ExtendsEveryGlobalRootSignatureSubobject) + TEST_METHOD(DebugInstrumentation_RawBufferShaderFlagDeclared) + TEST_METHOD(ToolsUav_RootSignatureSerializationFailurePreservesSignature) + TEST_METHOD(ToolsUav_ExtendingRootSignaturePreservesUnrelatedParameterFlags) + TEST_METHOD(ConstantColor_UnusedIntOverloadIsErased) + TEST_METHOD(ConstantColor_NoTargetOverloadsAreErased) + TEST_METHOD(RemoveDiscards_UnusedDiscardOverloadIsErased) + TEST_METHOD(OperationCacheCleanup_RemovesErasedFunctions) + TEST_METHOD(DynamicResourceCleanup_VisitorStopsEarly) TEST_METHOD(DxilPIXDXRInvocationsLog_SanityTest) TEST_METHOD(DxilPIXDXRInvocationsLog_EmbeddedRootSigs) @@ -158,6 +173,7 @@ class PixTest : public ::testing::Test { TEST_METHOD(DebugBreakInstrumentation_Multiple) TEST_METHOD(NonUniformResourceIndex_Resource) + TEST_METHOD(NonUniformResourceIndex_QualifiedCleanupValidates) TEST_METHOD(NonUniformResourceIndex_DescriptorHeap) TEST_METHOD(NonUniformResourceIndex_Raytracing) @@ -285,6 +301,7 @@ class PixTest : public ::testing::Test { std::vector Options; Options.push_back(L"-opt-mod-passes"); Options.push_back(PassOption); + Options.push_back(L"-hlsl-dxilemit"); CComPtr OptimizedModule; CComPtr Text; @@ -639,6 +656,12 @@ class PixTest : public ::testing::Test { } void ValidateAccessTrackingMods(const char *hlsl, bool modsExpected); + void loadSubobjectsFromContainerIntoModule(IDxcBlob *Container, + DxilModule &DM); + void verifyGlobalRootSignaturesHaveToolsUAVs( + DxilSubobjects *Subobjects, + const std::vector &ExpectedRootSignatureNames, + const std::vector &ExpectedShaderRegisters); class ModuleAndHangersOn { std::unique_ptr llvmContext; @@ -722,10 +745,11 @@ class PixTest : public ::testing::Test { void ValidateAllocaWrite(std::vector const &allocaWrites, size_t index, const char *name); PassOutput RunShaderAccessTrackingPass(IDxcBlob *blob); - std::string RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob); + CComPtr + RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob); CComPtr RunDxilPIXMeshShaderOutputPass(IDxcBlob *blob); CComPtr RunDxilPIXDXRInvocationsLog(IDxcBlob *blob); - std::vector + PassOutput RunDxilNonUniformResourceIndexInstrumentation(IDxcBlob *blob, std::string &outputText); void TestNuriCase(const char *source, const wchar_t *target, @@ -743,6 +767,125 @@ bool PixTest::InitSupport() { return true; } +static unsigned countToolsUAVs(DxilModule &DM) { + unsigned Count = 0; + for (const std::unique_ptr &UAV : DM.GetUAVs()) { + if (UAV->GetSpaceID() == static_cast(-2)) { + Count++; + } + } + return Count; +} + +static int countToolsUAVRecords(std::vector const &Lines) { + int Count = 0; + for (const std::string &Line : Lines) { + if (!Line.empty() && Line[0] == '!' && + Line.find(", i32 -2, i32 ") != std::string::npos) { + Count++; + } + } + return Count; +} + +static bool +rootSignatureHasToolsUAV(const DxilVersionedRootSignatureDesc *RootSignature, + uint32_t ShaderRegister) { + switch (RootSignature->Version) { + case DxilRootSignatureVersion::Version_1_0: { + const DxilRootSignatureDesc &Desc = RootSignature->Desc_1_0; + for (uint32_t ParameterIndex = 0; ParameterIndex < Desc.NumParameters; + ++ParameterIndex) { + const DxilRootParameter &Parameter = Desc.pParameters[ParameterIndex]; + if (Parameter.ParameterType == DxilRootParameterType::UAV && + Parameter.Descriptor.RegisterSpace == static_cast(-2) && + Parameter.Descriptor.ShaderRegister == ShaderRegister) { + return true; + } + } + break; + } + case DxilRootSignatureVersion::Version_1_1: { + const DxilRootSignatureDesc1 &Desc = RootSignature->Desc_1_1; + for (uint32_t ParameterIndex = 0; ParameterIndex < Desc.NumParameters; + ++ParameterIndex) { + const DxilRootParameter1 &Parameter = Desc.pParameters[ParameterIndex]; + if (Parameter.ParameterType == DxilRootParameterType::UAV && + Parameter.Descriptor.RegisterSpace == static_cast(-2) && + Parameter.Descriptor.ShaderRegister == ShaderRegister) { + return true; + } + } + break; + } + } + return false; +} + +void PixTest::loadSubobjectsFromContainerIntoModule(IDxcBlob *Container, + DxilModule &DM) { + const char *BlobContent = + reinterpret_cast(Container->GetBufferPointer()); + const unsigned BlobSize = Container->GetBufferSize(); + const hlsl::DxilContainerHeader *ContainerHeader = + hlsl::IsDxilContainerLike(BlobContent, BlobSize); + VERIFY_ARE_NOT_EQUAL(ContainerHeader, nullptr); + + const hlsl::DxilPartHeader *PartHeader = + GetDxilPartByType(ContainerHeader, hlsl::DFCC_RuntimeData); + VERIFY_ARE_NOT_EQUAL(PartHeader, nullptr); + + hlsl::RDAT::DxilRuntimeData RuntimeData(GetDxilPartData(PartHeader), + PartHeader->PartSize); + std::unique_ptr Subobjects(new DxilSubobjects()); + VERIFY_IS_TRUE(LoadSubobjectsFromRDAT(*Subobjects, RuntimeData)); + DM.ResetSubobjects(Subobjects.release()); +} + +void PixTest::verifyGlobalRootSignaturesHaveToolsUAVs( + DxilSubobjects *Subobjects, + const std::vector &ExpectedRootSignatureNames, + const std::vector &ExpectedShaderRegisters) { + VERIFY_IS_NOT_NULL(Subobjects); + + std::map FoundRootSignatures; + for (const std::string &RootSignatureName : ExpectedRootSignatureNames) { + FoundRootSignatures[RootSignatureName] = false; + } + + for (auto const &Subobject : Subobjects->GetSubobjects()) { + if (Subobject.second->GetKind() != + hlsl::DXIL::SubobjectKind::GlobalRootSignature) { + continue; + } + + const std::string SubobjectName = Subobject.first.str(); + if (FoundRootSignatures.find(SubobjectName) == FoundRootSignatures.end()) { + continue; + } + + const void *Data = nullptr; + uint32_t Size = 0; + constexpr bool NotALocalRootSignature = false; + VERIFY_IS_TRUE(Subobject.second->GetRootSignature(NotALocalRootSignature, + Data, Size, nullptr)); + + DxilVersionedRootSignatureDesc const *RootSignature = nullptr; + DeserializeRootSignature(Data, Size, &RootSignature); + for (uint32_t ExpectedShaderRegister : ExpectedShaderRegisters) { + VERIFY_IS_TRUE( + rootSignatureHasToolsUAV(RootSignature, ExpectedShaderRegister)); + } + DeleteRootSignature(RootSignature); + FoundRootSignatures[SubobjectName] = true; + } + + for (const std::map::value_type &FoundRootSignature : + FoundRootSignatures) { + VERIFY_IS_TRUE(FoundRootSignature.second); + } +} + void PixTest::TestPixUAVCase(char const *hlsl, wchar_t const *model, wchar_t const *entry) { auto mod = Compile(m_dllSupport, hlsl, model, {}, entry); @@ -957,7 +1100,7 @@ CComPtr PixTest::RunDxilPIXDXRInvocationsLog(IDxcBlob *blob) { return pOptimizedModule; } -std::vector PixTest::RunDxilNonUniformResourceIndexInstrumentation( +PassOutput PixTest::RunDxilNonUniformResourceIndexInstrumentation( IDxcBlob *blob, std::string &outputText) { CComPtr dxil = FindModule(DFCC_ShaderDebugInfoDXIL, blob); @@ -976,11 +1119,13 @@ std::vector PixTest::RunDxilNonUniformResourceIndexInstrumentation( outputText = BlobToUtf8(pText); - const std::string disassembly = Disassemble(pOptimizedModule); - return Tokenize(disassembly, "\n"); + PassOutput Result; + Result.blob = pOptimizedModule; + Result.lines = Tokenize(Disassemble(pOptimizedModule), "\n"); + return Result; } -std::string +CComPtr PixTest::RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob) { CComPtr dxil = FindModule(DFCC_ShaderDebugInfoDXIL, blob); CComPtr pOptimizer; @@ -996,14 +1141,16 @@ PixTest::RunDxilPIXAddTidToAmplificationShaderPayloadPass(IDxcBlob *blob) { VERIFY_SUCCEEDED(pOptimizer->RunOptimizer( dxil, Options.data(), Options.size(), &pOptimizedModule, &pText)); - std::string outputText; - if (pText->GetBufferSize() != 0) { - outputText = reinterpret_cast(pText->GetBufferPointer()); - } - - return outputText; + return pOptimizedModule; } +static bool hasDeclaration(const std::string &Disassembly, + const std::string &FunctionName); +static std::string findDeclarationLine(const std::string &Disassembly, + const std::string &FunctionName); +static bool hasDeclarationLine(const std::string &Disassembly, + const std::string &Declaration); + TEST_F(PixTest, AddToASPayload) { const char *hlsl = R"( @@ -1045,10 +1192,30 @@ void MSMain( )"; auto as = Compile(m_dllSupport, hlsl, L"as_6_6", {}, L"ASMain"); - RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); + const std::string OriginalDispatchMeshDeclaration = + findDeclarationLine(Disassemble(as), "dx.op.dispatchMesh"); + VERIFY_IS_FALSE(OriginalDispatchMeshDeclaration.empty()); + + CComPtr ASOutput = + RunDxilPIXAddTidToAmplificationShaderPayloadPass(as); + VERIFY_IS_FALSE(hasDeclarationLine(Disassemble(ASOutput), + OriginalDispatchMeshDeclaration)); auto ms = Compile(m_dllSupport, hlsl, L"ms_6_6", {}, L"MSMain"); - RunDxilPIXMeshShaderOutputPass(ms); + const std::string OriginalGetMeshPayloadDeclaration = + findDeclarationLine(Disassemble(ms), "dx.op.getMeshPayload"); + VERIFY_IS_FALSE(OriginalGetMeshPayloadDeclaration.empty()); + + CComPtr MSOutput = RunDxilPIXMeshShaderOutputPass(ms); + const std::string MeshDisassembly = Disassemble(MSOutput); + VERIFY_IS_FALSE( + hasDeclarationLine(MeshDisassembly, OriginalGetMeshPayloadDeclaration)); + VERIFY_IS_FALSE( + hasDeclaration(MeshDisassembly, "dx.op.storeVertexOutput.i32")); + VERIFY_IS_FALSE( + hasDeclaration(MeshDisassembly, "dx.op.storeVertexOutput.i16")); + VERIFY_IS_FALSE( + hasDeclaration(MeshDisassembly, "dx.op.storeVertexOutput.f16")); } unsigned FindOrAddVSInSignatureElementForInstanceOrVertexID( hlsl::DxilSignature &InputSignature, hlsl::DXIL::SemanticKind semanticKind); @@ -3126,6 +3293,459 @@ float4 main(int i : A, float j : B) : SV_TARGET VERIFY_IS_TRUE(foundGlobalRS); } +TEST_F(PixTest, ToolsUav_TwoPixPassesShareOneResource) { + const char *Source = R"x( +RWByteAddressBuffer output : register(u0); + +[numthreads(1, 1, 1)] +void main(uint3 tid : SV_DispatchThreadID) +{ + output.Store(4 * tid.x, tid.x); +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"cs_6_2", {L"-Od"}); + PassOutput DebugOutput = RunDebugPass(Compiled); + PassOutput AccessOutput = RunShaderAccessTrackingPass(DebugOutput.blob); + + ModuleAndHangersOn ModuleEtc(AccessOutput.blob); + VERIFY_ARE_EQUAL(1u, countToolsUAVs(ModuleEtc.GetDxilModule())); + verifyInstrumentedModuleIsValid( + AccessOutput.blob, + "debug instrumentation followed by shader access tracking"); +} + +TEST_F(PixTest, ToolsUav_LibraryWithTwoEntryPointsCreatesOnePair) { + const char *Source = R"x( +struct [raypayload] MyPayload +{ + float2 barycentrics : read(caller) : write(caller,anyhit); + uint primitiveIndex : read(caller) : write(caller,anyhit); +}; + +[shader("miss")] +void MissOne(inout MyPayload payload) +{ + payload.primitiveIndex = 1; +} + +[shader("miss")] +void MissTwo(inout MyPayload payload) +{ + payload.primitiveIndex = 2; +} +)x"; + + CComPtr Compiled = Compile(m_dllSupport, Source, L"lib_6_6", {}); + CComPtr Output = RunDxilPIXDXRInvocationsLog(Compiled); + + std::vector Lines = Tokenize(Disassemble(Output), "\n"); + VERIFY_ARE_EQUAL(2, countToolsUAVRecords(Lines)); +} + +TEST_F(PixTest, ToolsUav_ExtendsEveryGlobalRootSignatureSubobject) { + const char *Source = R"x( +GlobalRootSignature firstRootSignature = {"CBV(b0)"}; +GlobalRootSignature secondRootSignature = {"SRV(t0)"}; + +SubobjectToExportsAssociation firstAssociation = +{ + "firstRootSignature", + "MyClosestHit" +}; + +SubobjectToExportsAssociation secondAssociation = +{ + "secondRootSignature", + "MyMiss" +}; + +struct MyPayload +{ + float4 color; +}; + +[shader("raygeneration")] +void MyRayGen() +{ +} + +[shader("closesthit")] +void MyClosestHit(inout MyPayload payload, + in BuiltInTriangleIntersectionAttributes attr) +{ +} + +[shader("miss")] +void MyMiss(inout MyPayload payload) +{ +} +)x"; + + CComPtr Compiled = Compile(m_dllSupport, Source, L"lib_6_6", {}); + ModuleAndHangersOn ModuleEtc(Compiled); + DxilModule &DM = ModuleEtc.GetDxilModule(); + loadSubobjectsFromContainerIntoModule(Compiled, DM); + PIXPassHelpers::CreateGlobalUAVResource(DM, 0, "PIX_CountUAV_Handle"); + PIXPassHelpers::CreateGlobalUAVResource(DM, 1, "PIX_LogUAV_Handle"); + + verifyGlobalRootSignaturesHaveToolsUAVs( + DM.GetSubobjects(), {"firstRootSignature", "secondRootSignature"}, + {0, 1}); +} + +TEST_F(PixTest, DebugInstrumentation_RawBufferShaderFlagDeclared) { + const char *Source = R"x( +[numthreads(1, 1, 1)] +void main(uint threadId : SV_DispatchThreadID) +{ +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"cs_6_2", {L"-Od"}); + PassOutput Output = RunDebugPass(Compiled); + std::vector Lines = Tokenize(Disassemble(Output.blob), "\n"); + + constexpr uint64_t EnableRawAndStructuredBuffers = 0x10; + bool FoundShaderFlags = false; + uint64_t ShaderFlags = 0; + const std::string TagPrefix = "!{i32 0, i64 "; + for (const std::string &Line : Lines) { + const std::string::size_type TagStart = Line.find(TagPrefix); + if (TagStart == std::string::npos) { + continue; + } + ShaderFlags = + strtoull(Line.c_str() + TagStart + TagPrefix.length(), nullptr, 10); + FoundShaderFlags = true; + break; + } + + VERIFY_IS_TRUE(FoundShaderFlags); + VERIFY_ARE_EQUAL(EnableRawAndStructuredBuffers, + ShaderFlags & EnableRawAndStructuredBuffers); + verifyInstrumentedModuleIsValid(Output.blob, + "debug instrumentation shader flags"); +} + +TEST_F(PixTest, ToolsUav_RootSignatureSerializationFailurePreservesSignature) { + const char *Source = R"x( +[numthreads(1, 1, 1)] +void main() +{ +})x"; + + DxilDescriptorRange Range = {}; + Range.RangeType = DxilDescriptorRangeType::UAV; + Range.NumDescriptors = 1; + Range.BaseShaderRegister = 0; + Range.RegisterSpace = static_cast(-2); + Range.OffsetInDescriptorsFromTableStart = DxilDescriptorRangeOffsetAppend; + + DxilRootParameter Parameter = {}; + Parameter.ParameterType = DxilRootParameterType::DescriptorTable; + Parameter.DescriptorTable.NumDescriptorRanges = 1; + Parameter.DescriptorTable.pDescriptorRanges = &Range; + Parameter.ShaderVisibility = DxilShaderVisibility::All; + + DxilVersionedRootSignatureDesc RootSignature = {}; + RootSignature.Version = DxilRootSignatureVersion::Version_1_0; + RootSignature.Desc_1_0.NumParameters = 1; + RootSignature.Desc_1_0.pParameters = &Parameter; + RootSignature.Desc_1_0.Flags = DxilRootSignatureFlags::None; + + CComPtr SerializedRootSignature; + CComPtr ErrorBlob; + SerializeRootSignature(&RootSignature, &SerializedRootSignature, &ErrorBlob, + true); + VERIFY_IS_NOT_NULL(SerializedRootSignature); + + const uint8_t *SerializedData = + static_cast(SerializedRootSignature->GetBufferPointer()); + std::vector OriginalRootSignature( + SerializedData, + SerializedData + SerializedRootSignature->GetBufferSize()); + + CComPtr Compiled = Compile(m_dllSupport, Source, L"cs_6_0", {}); + ModuleAndHangersOn ModuleEtc(Compiled); + DxilModule &DM = ModuleEtc.GetDxilModule(); + DM.ResetSerializedRootSignature(OriginalRootSignature); + + std::unique_ptr Subobjects(new DxilSubobjects()); + constexpr bool NotALocalRootSignature = false; + Subobjects->CreateRootSignature( + "testRootSignature", NotALocalRootSignature, OriginalRootSignature.data(), + static_cast(OriginalRootSignature.size())); + DM.ResetSubobjects(Subobjects.release()); + + PIXPassHelpers::CreateGlobalUAVResource(DM, 0, "PIX_TestUAV"); + + const std::vector &ActualRootSignature = + DM.GetSerializedRootSignature(); + VERIFY_ARE_EQUAL(OriginalRootSignature.size(), ActualRootSignature.size()); + VERIFY_IS_TRUE(std::equal(OriginalRootSignature.begin(), + OriginalRootSignature.end(), + ActualRootSignature.begin())); + + bool FoundRootSignature = false; + for (auto const &Subobject : DM.GetSubobjects()->GetSubobjects()) { + if (Subobject.first != "testRootSignature") { + continue; + } + + const void *Data = nullptr; + uint32_t Size = 0; + VERIFY_IS_TRUE(Subobject.second->GetRootSignature(NotALocalRootSignature, + Data, Size, nullptr)); + VERIFY_ARE_EQUAL(OriginalRootSignature.size(), static_cast(Size)); + VERIFY_IS_TRUE(std::equal(OriginalRootSignature.begin(), + OriginalRootSignature.end(), + static_cast(Data))); + FoundRootSignature = true; + } + VERIFY_IS_TRUE(FoundRootSignature); +} + +TEST_F(PixTest, + ToolsUav_ExtendingRootSignaturePreservesUnrelatedParameterFlags) { + const char *Source = R"x( +[numthreads(1, 1, 1)] +void main() +{ +})x"; + + DxilRootParameter1 Parameters[2] = {}; + Parameters[0].ParameterType = DxilRootParameterType::UAV; + Parameters[0].Descriptor.RegisterSpace = static_cast(-2); + Parameters[0].Descriptor.ShaderRegister = 0; + Parameters[0].Descriptor.Flags = DxilRootDescriptorFlags::None; + Parameters[0].ShaderVisibility = DxilShaderVisibility::All; + + Parameters[1].ParameterType = DxilRootParameterType::CBV; + Parameters[1].Descriptor.RegisterSpace = 0; + Parameters[1].Descriptor.ShaderRegister = 0; + Parameters[1].Descriptor.Flags = DxilRootDescriptorFlags::DataVolatile; + Parameters[1].ShaderVisibility = DxilShaderVisibility::All; + + DxilVersionedRootSignatureDesc RootSignature = {}; + RootSignature.Version = DxilRootSignatureVersion::Version_1_1; + RootSignature.Desc_1_1.NumParameters = 2; + RootSignature.Desc_1_1.pParameters = Parameters; + RootSignature.Desc_1_1.Flags = DxilRootSignatureFlags::None; + + CComPtr SerializedRootSignature; + CComPtr ErrorBlob; + SerializeRootSignature(&RootSignature, &SerializedRootSignature, &ErrorBlob, + true); + VERIFY_IS_NOT_NULL(SerializedRootSignature); + + const uint8_t *SerializedData = + static_cast(SerializedRootSignature->GetBufferPointer()); + std::vector OriginalRootSignature( + SerializedData, + SerializedData + SerializedRootSignature->GetBufferSize()); + + CComPtr Compiled = Compile(m_dllSupport, Source, L"cs_6_0", {}); + ModuleAndHangersOn ModuleEtc(Compiled); + DxilModule &DM = ModuleEtc.GetDxilModule(); + DM.ResetSerializedRootSignature(OriginalRootSignature); + + PIXPassHelpers::CreateGlobalUAVResource(DM, 0, "PIX_TestUAV0"); + + { + const std::vector &Bytes = DM.GetSerializedRootSignature(); + DxilVersionedRootSignatureDesc const *AfterNoOp = nullptr; + DeserializeRootSignature(Bytes.data(), static_cast(Bytes.size()), + &AfterNoOp); + VERIFY_ARE_EQUAL(AfterNoOp->Desc_1_1.NumParameters, 2u); + VERIFY_IS_TRUE(AfterNoOp->Desc_1_1.pParameters[1].Descriptor.Flags == + DxilRootDescriptorFlags::DataVolatile); + DeleteRootSignature(AfterNoOp); + } + + PIXPassHelpers::CreateGlobalUAVResource(DM, 1, "PIX_TestUAV1"); + + { + const std::vector &Bytes = DM.GetSerializedRootSignature(); + DxilVersionedRootSignatureDesc const *AfterAdd = nullptr; + DeserializeRootSignature(Bytes.data(), static_cast(Bytes.size()), + &AfterAdd); + VERIFY_ARE_EQUAL(AfterAdd->Desc_1_1.NumParameters, 3u); + VERIFY_IS_TRUE(AfterAdd->Desc_1_1.pParameters[1].Descriptor.Flags == + DxilRootDescriptorFlags::DataVolatile); + VERIFY_ARE_EQUAL(AfterAdd->Desc_1_1.pParameters[2].Descriptor.RegisterSpace, + static_cast(-2)); + VERIFY_ARE_EQUAL( + AfterAdd->Desc_1_1.pParameters[2].Descriptor.ShaderRegister, 1u); + VERIFY_IS_TRUE(AfterAdd->Desc_1_1.pParameters[2].Descriptor.Flags == + DxilRootDescriptorFlags::None); + DeleteRootSignature(AfterAdd); + } +} + +static bool hasUnusedDeclaration(std::vector const &Lines, + std::string const &FunctionName) { + bool Declared = false; + for (const std::string &Line : Lines) { + if (Line.find("declare") != std::string::npos && + Line.find(FunctionName) != std::string::npos) { + Declared = true; + } + if (Line.find("call") != std::string::npos && + Line.find(FunctionName) != std::string::npos) { + return false; + } + } + return Declared; +} + +static bool hasDeclaration(const std::string &Disassembly, + const std::string &FunctionName) { + for (const std::string &Line : Tokenize(Disassembly, "\n")) { + if (Line.find("declare") != std::string::npos && + Line.find(FunctionName) != std::string::npos) { + return true; + } + } + return false; +} + +static std::string findDeclarationLine(const std::string &Disassembly, + const std::string &FunctionName) { + for (const std::string &Line : Tokenize(Disassembly, "\n")) { + if (Line.find("declare") != std::string::npos && + Line.find(FunctionName) != std::string::npos) { + return Line; + } + } + return {}; +} + +static bool hasDeclarationLine(const std::string &Disassembly, + const std::string &Declaration) { + for (const std::string &Line : Tokenize(Disassembly, "\n")) { + if (Line == Declaration) { + return true; + } + } + return false; +} + +TEST_F(PixTest, ConstantColor_UnusedIntOverloadIsErased) { + const char *Source = R"x( +float4 main() : SV_Target +{ + return float4(1, 2, 3, 4); +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"ps_6_0", {L"-Od"}); + SinglePassOutput Output = + runSinglePass(Compiled, L"-hlsl-dxil-constantColor"); + + VERIFY_IS_FALSE(hasUnusedDeclaration(Output.Lines, "dx.op.storeOutput.i32")); + verifyInstrumentedModuleIsValid(Output.Module, + "constant-colour substitution"); +} + +TEST_F(PixTest, ConstantColor_NoTargetOverloadsAreErased) { + const char *Source = R"x( +[numthreads(1, 1, 1)] +void main() +{ +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"cs_6_0", {L"-Od"}); + SinglePassOutput Output = + runSinglePass(Compiled, L"-hlsl-dxil-constantColor"); + const std::string Disassembly = Disassemble(Output.Module); + + verifyInstrumentedModuleIsValid( + Output.Module, "constant-colour substitution with no target"); + VERIFY_IS_FALSE(hasDeclaration(Disassembly, "dx.op.storeOutput.f32")); + VERIFY_IS_FALSE(hasDeclaration(Disassembly, "dx.op.storeOutput.i32")); +} + +TEST_F(PixTest, RemoveDiscards_UnusedDiscardOverloadIsErased) { + const char *Source = R"x( +float4 main() : SV_Target +{ + return float4(1, 2, 3, 4); +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"ps_6_0", {L"-Od"}); + SinglePassOutput Output = + runSinglePass(Compiled, L"-hlsl-dxil-remove-discards"); + + VERIFY_IS_FALSE(hasUnusedDeclaration(Output.Lines, "dx.op.discard")); + verifyInstrumentedModuleIsValid(Output.Module, + "discard removal with no discard"); +} + +TEST_F(PixTest, OperationCacheCleanup_RemovesErasedFunctions) { + const char *Source = R"x( +float4 main() : SV_Target +{ + return float4(1, 2, 3, 4); +})x"; + + CComPtr Compiled = Compile(m_dllSupport, Source, L"ps_6_0", {}); + ModuleAndHangersOn ModuleEtc(Compiled); + DxilModule &DM = ModuleEtc.GetDxilModule(); + OP *HlslOP = DM.GetOP(); + llvm::Function *Discard = + HlslOP->GetOpFunc(DXIL::OpCode::Discard, + llvm::Type::getVoidTy(DM.GetModule()->getContext())); + + VERIFY_ARE_EQUAL(1u, + static_cast( + HlslOP->GetOpFuncList(DXIL::OpCode::Discard).size())); + PIXPassHelpers::eraseIfUnused(DM, Discard); + VERIFY_ARE_EQUAL(0u, + static_cast( + HlslOP->GetOpFuncList(DXIL::OpCode::Discard).size())); + + llvm::Function *Recreated = + HlslOP->GetOpFunc(DXIL::OpCode::Discard, + llvm::Type::getVoidTy(DM.GetModule()->getContext())); + VERIFY_IS_NOT_NULL(Recreated); + PIXPassHelpers::eraseIfUnused(DM, Recreated); +} + +TEST_F(PixTest, DynamicResourceCleanup_VisitorStopsEarly) { + const char *Source = R"x( +Texture2D textures[] : register(t0); + +float4 main(float2 uv : TEXCOORD0) : SV_Target +{ + return textures[(uint)uv.x].Load(int3(0, 0, 0)); +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"ps_6_0", {L"-Od"}); + ModuleAndHangersOn ModuleEtc(Compiled); + DxilModule &DM = ModuleEtc.GetDxilModule(); + bool VisitorCalled = false; + PIXPassHelpers::ForEachDynamicallyIndexedResource( + DM, [&VisitorCalled](bool, llvm::Instruction *, llvm::Value *) { + VisitorCalled = true; + return false; + }); + + VERIFY_IS_TRUE(VisitorCalled); + OP *HlslOP = DM.GetOP(); + VERIFY_ARE_EQUAL( + 0u, + static_cast( + HlslOP->GetOpFuncList(DXIL::OpCode::CreateHandleFromBinding).size())); + VERIFY_ARE_EQUAL( + 0u, + static_cast( + HlslOP->GetOpFuncList(DXIL::OpCode::CreateHandleFromHeap).size())); +} + TEST_F(PixTest, DxilPIXDXRInvocationsLog_SanityTest) { const char *source = R"x( @@ -3219,8 +3839,9 @@ void PixTest::TestNuriCase(const char *source, const wchar_t *target, Compile(m_dllSupport, source, target, compilationOptions); std::string outputText; - const std::vector dxilLines = + PassOutput Output = RunDxilNonUniformResourceIndexInstrumentation(compiledLib, outputText); + const std::vector &dxilLines = Output.lines; VERIFY_ARE_EQUAL(NuriGetWaveInstructionCount(dxilLines), expectedResult); @@ -3266,6 +3887,34 @@ float4 main(float2 uv : TEXCOORD0) : SV_TARGET TestNuriCase(sourceWithNuri, L"ps_6_6", 0); } +TEST_F(PixTest, NonUniformResourceIndex_QualifiedCleanupValidates) { + if (m_ver.SkipDxilVersion(1, 6)) { + return; + } + + const char *Source = R"x( +Texture2D textures[] : register(t0); + +float4 main(float2 uv : TEXCOORD0) : SV_Target +{ + uint index = (uint)uv.x; + return textures[NonUniformResourceIndex(index)].Load(int3(0, 0, 0)); +})x"; + + CComPtr Compiled = + Compile(m_dllSupport, Source, L"ps_6_6", {L"-Od"}); + std::string OutputText; + PassOutput Output = + RunDxilNonUniformResourceIndexInstrumentation(Compiled, OutputText); + const std::string Disassembly = Disassemble(Output.blob); + + verifyInstrumentedModuleIsValid( + Output.blob, "qualified non-uniform resource index instrumentation"); + VERIFY_ARE_EQUAL(0u, NuriGetWaveInstructionCount(Output.lines)); + VERIFY_IS_FALSE(hasDeclaration(Disassembly, "dx.op.waveActiveAllEqual.i32")); + VERIFY_IS_FALSE(hasDeclaration(Disassembly, "dx.op.atomicBinOp.i32")); +} + TEST_F(PixTest, NonUniformResourceIndex_DescriptorHeap) { if (m_ver.SkipDxilVersion(1, 6)) { @@ -3651,7 +4300,7 @@ void main() { DebugBreak(); })x"; - auto compiled = Compile(m_dllSupport, source, L"cs_6_10", {}); + CComPtr compiled = Compile(m_dllSupport, source, L"cs_6_10", {}); auto output = RunDebugBreakPass(compiled); bool foundDebugBreak = false; for (auto const &line : output.lines) { @@ -3662,15 +4311,15 @@ void main() { } TEST_F(PixTest, DebugBreakInstrumentation_NoDebugBreak) { + if (m_ver.SkipDxilVersion(1, 10)) + return; const char *source = R"x( -RWByteAddressBuffer buf : register(u0); [numthreads(1, 1, 1)] void main() { - buf.Store(0, 1); })x"; - auto compiled = Compile(m_dllSupport, source, L"cs_6_0", {}); + auto compiled = Compile(m_dllSupport, source, L"cs_6_10", {}); auto output = RunDebugBreakPass(compiled); bool foundDebugBreak = false; for (auto const &line : output.lines) { @@ -3678,6 +4327,8 @@ void main() { foundDebugBreak = true; } VERIFY_IS_FALSE(foundDebugBreak); + verifyInstrumentedModuleIsValid(output.blob, + "debug-break instrumentation with no call"); } TEST_F(PixTest, DebugBreakInstrumentation_Multiple) {