| /////////////////////////////////////////////////////////////////////////////// |
| // // |
| // DxilContainerValidation.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. // |
| // // |
| // This file provides support for validating DXIL container. // |
| // // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #include "dxc/Support/FileIOHelper.h" |
| #include "dxc/Support/Global.h" |
| #include "dxc/Support/WinIncludes.h" |
| |
| #include "dxc/DxilContainer/DxilContainer.h" |
| #include "dxc/DxilContainer/DxilContainerAssembler.h" |
| #include "dxc/DxilContainer/DxilPipelineStateValidation.h" |
| #include "dxc/DxilContainer/DxilRuntimeReflection.h" |
| #include "dxc/DxilRootSignature/DxilRootSignature.h" |
| #include "dxc/DxilValidation/DxilValidation.h" |
| |
| #include "dxc/DXIL/DxilModule.h" |
| #include "dxc/DXIL/DxilUtil.h" |
| |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/BitVector.h" |
| #include "llvm/Bitcode/ReaderWriter.h" |
| #include "llvm/IR/DiagnosticPrinter.h" |
| #include "llvm/IR/Module.h" |
| #include "llvm/Support/MemoryBuffer.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include "DxilValidationUtils.h" |
| |
| #include <memory> |
| #include <unordered_map> |
| #include <unordered_set> |
| |
| using namespace llvm; |
| using namespace hlsl; |
| |
| using std::unique_ptr; |
| using std::unordered_set; |
| using std::vector; |
| |
| namespace { |
| |
| // Utility class for setting and restoring the diagnostic context so we may |
| // capture errors/warnings |
| struct DiagRestore { |
| LLVMContext *Ctx = nullptr; |
| void *OrigDiagContext; |
| LLVMContext::DiagnosticHandlerTy OrigHandler; |
| |
| DiagRestore(llvm::LLVMContext &InputCtx, void *DiagContext) : Ctx(&InputCtx) { |
| init(DiagContext); |
| } |
| DiagRestore(Module *M, void *DiagContext) { |
| if (!M) |
| return; |
| Ctx = &M->getContext(); |
| init(DiagContext); |
| } |
| ~DiagRestore() { |
| if (!Ctx) |
| return; |
| Ctx->setDiagnosticHandler(OrigHandler, OrigDiagContext); |
| } |
| |
| private: |
| void init(void *DiagContext) { |
| OrigHandler = Ctx->getDiagnosticHandler(); |
| OrigDiagContext = Ctx->getDiagnosticContext(); |
| Ctx->setDiagnosticHandler( |
| hlsl::PrintDiagnosticContext::PrintDiagnosticHandler, DiagContext); |
| } |
| }; |
| |
| static void emitDxilDiag(LLVMContext &Ctx, const char *str) { |
| hlsl::dxilutil::EmitErrorOnContext(Ctx, str); |
| } |
| |
| class StringTableVerifier { |
| std::unordered_map<unsigned, unsigned> OffsetToUseCountMap; |
| const PSVStringTable &Table; |
| |
| public: |
| StringTableVerifier(const PSVStringTable &Table) : Table(Table) { |
| unsigned Start = 0; |
| for (unsigned i = 0; i < Table.Size; ++i) { |
| char ch = Table.Table[i]; |
| if (ch == '\0') { |
| OffsetToUseCountMap[Start] = 0; |
| Start = i + 1; |
| } |
| } |
| if (Table.Size >= 4) { |
| // Remove the '\0's at the end of the table added for padding. |
| for (unsigned i = Table.Size - 1; i > Table.Size - 4; --i) { |
| if (Table.Table[i] != '\0') |
| break; |
| OffsetToUseCountMap.erase(i); |
| } |
| } |
| } |
| bool MarkUse(unsigned Offset) { |
| auto it = OffsetToUseCountMap.find(Offset); |
| if (it != OffsetToUseCountMap.end()) |
| it->second++; |
| return Offset < Table.Size; |
| } |
| void Verify(ValidationContext &ValCtx) { |
| for (auto [Offset, UseCount] : OffsetToUseCountMap) { |
| if (UseCount != 0) |
| continue; |
| // DXC will always add a null-terminated string at the beginning of the |
| // StringTable. It is OK if it is not used. |
| if (Offset == 0 && Table.Table[0] == '\0') |
| continue; |
| |
| ValCtx.EmitFormatError(ValidationRule::ContainerUnusedItemInTable, |
| {"StringTable", Table.Get(Offset)}); |
| } |
| } |
| }; |
| |
| class SemanticIndexTableVerifier { |
| const PSVSemanticIndexTable &Table; |
| llvm::BitVector UseMask; |
| |
| public: |
| SemanticIndexTableVerifier(const PSVSemanticIndexTable &Table) |
| : Table(Table), UseMask(Table.Entries, false) {} |
| bool MarkUse(unsigned Offset, unsigned Size) { |
| if (Table.Table == nullptr) |
| return false; |
| if (Offset > Table.Entries) |
| return false; |
| if ((Offset + Size) > Table.Entries) |
| return false; |
| for (unsigned i = Offset; i < (Offset + Size); ++i) { |
| UseMask[i] = true; |
| } |
| return true; |
| } |
| void Verify(ValidationContext &ValCtx) { |
| for (unsigned i = 0; i < Table.Entries; i++) { |
| if (UseMask[i]) |
| continue; |
| |
| ValCtx.EmitFormatError(ValidationRule::ContainerUnusedItemInTable, |
| {"SemanticIndexTable", std::to_string(i)}); |
| } |
| } |
| }; |
| |
| class PSVContentVerifier { |
| DxilModule &DM; |
| DxilPipelineStateValidation &PSV; |
| ValidationContext &ValCtx; |
| bool PSVContentValid = true; |
| StringTableVerifier StrTableVerifier; |
| SemanticIndexTableVerifier IndexTableVerifier; |
| |
| public: |
| PSVContentVerifier(DxilPipelineStateValidation &PSV, DxilModule &DM, |
| ValidationContext &ValCtx) |
| : DM(DM), PSV(PSV), ValCtx(ValCtx), |
| StrTableVerifier(PSV.GetStringTable()), |
| IndexTableVerifier(PSV.GetSemanticIndexTable()) {} |
| void Verify(unsigned ValMajor, unsigned ValMinor, unsigned PSVVersion); |
| |
| private: |
| void VerifySignatures(unsigned ValMajor, unsigned ValMinor); |
| void VerifySignature(const DxilSignature &, PSVSignatureElement0 *Base, |
| unsigned Count, std::string Name, |
| bool i1ToUnknownCompat); |
| void VerifySignatureElement(const DxilSignatureElement &, |
| PSVSignatureElement0 *, const PSVStringTable &, |
| const PSVSemanticIndexTable &, std::string, bool); |
| void VerifyResources(unsigned PSVVersion); |
| template <typename T> |
| void VerifyResourceTable(T &ResTab, unsigned &ResourceIndex, |
| unsigned PSVVersion); |
| void VerifyViewIDDependence(PSVRuntimeInfo1 *PSV1, unsigned PSVVersion); |
| void VerifyEntryProperties(const ShaderModel *SM, PSVRuntimeInfo0 *PSV0, |
| PSVRuntimeInfo1 *PSV1, PSVRuntimeInfo2 *PSV2, |
| PSVRuntimeInfo3 *PSV3, PSVRuntimeInfo4 *PSV4); |
| void EmitMismatchError(StringRef Name, StringRef PartContent, |
| StringRef ModuleContent) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentMatches, |
| {Name, "PSV0", PartContent, ModuleContent}); |
| PSVContentValid = false; |
| } |
| void EmitInvalidError(StringRef Name) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentInvalid, |
| {"PSV0 part", Name}); |
| PSVContentValid = false; |
| } |
| template <typename Ty> static std::string GetDump(const Ty &T) { |
| std::string Str; |
| raw_string_ostream OS(Str); |
| T.Print(OS); |
| OS.flush(); |
| return Str; |
| } |
| }; |
| |
| void PSVContentVerifier::VerifyViewIDDependence(PSVRuntimeInfo1 *PSV1, |
| unsigned PSVVersion) { |
| std::vector<unsigned int> ViewStateInPSV; |
| unsigned OutputSizeInUInts = hlsl::LoadViewIDStateFromPSV(nullptr, 0, PSV); |
| if (OutputSizeInUInts) { |
| ViewStateInPSV.assign(OutputSizeInUInts, 0); |
| hlsl::LoadViewIDStateFromPSV(ViewStateInPSV.data(), |
| (unsigned)ViewStateInPSV.size(), PSV); |
| } |
| // In case the num of input/output scalars are aligned to 4, the |
| // ViewStateInPSV could match the DxilModule's view state directly. |
| std::vector<unsigned int> ViewStateInDxilModule = |
| DM.GetSerializedViewIdState(); |
| if (ViewStateInPSV == ViewStateInDxilModule) |
| return; |
| if (ViewStateInDxilModule.empty() && |
| std::all_of(ViewStateInPSV.begin(), ViewStateInPSV.end(), |
| [](unsigned int i) { return i == 0; })) |
| return; |
| |
| std::string Str; |
| raw_string_ostream OS(Str); |
| PSV.PrintViewIDState(OS); |
| OS.flush(); |
| |
| // Create a Temp PSV from DxilModule to print the ViewIDState. |
| unique_ptr<DxilPartWriter> pWriter(NewPSVWriter(DM, PSVVersion)); |
| CComPtr<AbstractMemoryStream> pOutputStream; |
| IFT(CreateMemoryStream(DxcGetThreadMallocNoRef(), &pOutputStream)); |
| pOutputStream->Reserve(pWriter->size()); |
| pWriter->write(pOutputStream); |
| |
| DxilPipelineStateValidation PSVFromDxilModule; |
| if (!PSVFromDxilModule.InitFromPSV0(pOutputStream->GetPtr(), |
| pOutputStream->GetPtrSize())) { |
| ValCtx.EmitFormatError( |
| ValidationRule::ContainerPartMatches, |
| {"Pipeline State Validation generated from DxiModule"}); |
| return; |
| } |
| |
| ViewStateInDxilModule.clear(); |
| OutputSizeInUInts = |
| hlsl::LoadViewIDStateFromPSV(nullptr, 0, PSVFromDxilModule); |
| if (OutputSizeInUInts) { |
| ViewStateInDxilModule.assign(OutputSizeInUInts, 0); |
| hlsl::LoadViewIDStateFromPSV(ViewStateInDxilModule.data(), |
| OutputSizeInUInts, PSVFromDxilModule); |
| } |
| // ViewStateInDxilModule and ViewStateInPSV all go through |
| // LoadViewIDStateFromPSV here, so they should match. |
| if (ViewStateInPSV == ViewStateInDxilModule) |
| return; |
| |
| std::string Str1; |
| raw_string_ostream OS1(Str1); |
| PSVFromDxilModule.PrintViewIDState(OS1); |
| OS1.flush(); |
| EmitMismatchError("ViewIDState", Str, Str1); |
| } |
| |
| void PSVContentVerifier::VerifySignatures(unsigned ValMajor, |
| unsigned ValMinor) { |
| bool i1ToUnknownCompat = DXIL::CompareVersions(ValMajor, ValMinor, 1, 5) < 0; |
| // Verify input signature |
| VerifySignature(DM.GetInputSignature(), PSV.GetInputElement0(0), |
| PSV.GetSigInputElements(), "SigInput", i1ToUnknownCompat); |
| // Verify output signature |
| VerifySignature(DM.GetOutputSignature(), PSV.GetOutputElement0(0), |
| PSV.GetSigOutputElements(), "SigOutput", i1ToUnknownCompat); |
| // Verify patch constant signature |
| VerifySignature(DM.GetPatchConstOrPrimSignature(), |
| PSV.GetPatchConstOrPrimElement0(0), |
| PSV.GetSigPatchConstOrPrimElements(), |
| "SigPatchConstantOrPrim", i1ToUnknownCompat); |
| } |
| |
| void PSVContentVerifier::VerifySignature(const DxilSignature &Sig, |
| PSVSignatureElement0 *Base, |
| unsigned Count, std::string Name, |
| bool i1ToUnknownCompat) { |
| if (Count != Sig.GetElements().size()) { |
| EmitMismatchError(Name + "Elements", std::to_string(Count), |
| std::to_string(Sig.GetElements().size())); |
| return; |
| } |
| |
| // Verify each element in DxilSignature. |
| const PSVStringTable &StrTab = PSV.GetStringTable(); |
| const PSVSemanticIndexTable &IndexTab = PSV.GetSemanticIndexTable(); |
| for (unsigned i = 0; i < Count; i++) |
| VerifySignatureElement(Sig.GetElement(i), |
| PSV.GetRecord<PSVSignatureElement0>( |
| Base, PSV.GetSignatureElementSize(), Count, i), |
| StrTab, IndexTab, Name, i1ToUnknownCompat); |
| } |
| |
| void PSVContentVerifier::VerifySignatureElement( |
| const DxilSignatureElement &SE, PSVSignatureElement0 *PSVSE0, |
| const PSVStringTable &StrTab, const PSVSemanticIndexTable &IndexTab, |
| std::string Name, bool i1ToUnknownCompat) { |
| bool InvalidTableAccess = false; |
| if (!StrTableVerifier.MarkUse(PSVSE0->SemanticName)) { |
| EmitInvalidError("SemanticName"); |
| InvalidTableAccess = true; |
| } |
| if (!IndexTableVerifier.MarkUse(PSVSE0->SemanticIndexes, PSVSE0->Rows)) { |
| EmitInvalidError("SemanticIndex"); |
| InvalidTableAccess = true; |
| } |
| if (InvalidTableAccess) |
| return; |
| // Find the signature element in the set. |
| PSVSignatureElement0 ModulePSVSE0; |
| InitPSVSignatureElement(ModulePSVSE0, SE, i1ToUnknownCompat); |
| |
| // Check the Name and SemanticIndex. |
| bool Mismatch = false; |
| const std::vector<uint32_t> &SemanticIndexVec = SE.GetSemanticIndexVec(); |
| llvm::ArrayRef<uint32_t> PSVSemanticIndexVec( |
| IndexTab.Get(PSVSE0->SemanticIndexes), PSVSE0->Rows); |
| if (SemanticIndexVec.size() == PSVSemanticIndexVec.size()) |
| Mismatch |= memcmp(PSVSemanticIndexVec.data(), SemanticIndexVec.data(), |
| SemanticIndexVec.size() * sizeof(uint32_t)) != 0; |
| else |
| Mismatch = true; |
| |
| ModulePSVSE0.SemanticIndexes = PSVSE0->SemanticIndexes; |
| |
| PSVSignatureElement PSVSE(StrTab, IndexTab, PSVSE0); |
| if (SE.IsArbitrary()) |
| Mismatch |= strcmp(PSVSE.GetSemanticName(), SE.GetName()) != 0; |
| else |
| Mismatch |= PSVSE0->SemanticKind != static_cast<uint8_t>(SE.GetKind()); |
| |
| ModulePSVSE0.SemanticName = PSVSE0->SemanticName; |
| // Compare all fields. |
| Mismatch |= memcmp(&ModulePSVSE0, PSVSE0, sizeof(PSVSignatureElement0)) != 0; |
| if (Mismatch) { |
| PSVSignatureElement ModulePSVSE(StrTab, IndexTab, &ModulePSVSE0); |
| std::string ModuleStr; |
| raw_string_ostream OS(ModuleStr); |
| ModulePSVSE.Print(OS, SE.GetName(), SemanticIndexVec.data()); |
| OS.flush(); |
| std::string PartStr; |
| raw_string_ostream OS1(PartStr); |
| PSVSE.Print(OS1, PSVSE.GetSemanticName(), PSVSemanticIndexVec.data()); |
| OS1.flush(); |
| EmitMismatchError(Name + "Element", PartStr, ModuleStr); |
| } |
| } |
| |
| template <typename T> |
| void PSVContentVerifier::VerifyResourceTable(T &ResTab, unsigned &ResourceIndex, |
| unsigned PSVVersion) { |
| for (auto &&R : ResTab) { |
| PSVResourceBindInfo1 BI; |
| InitPSVResourceBinding(&BI, &BI, R.get()); |
| |
| if (PSVVersion > 1) { |
| PSVResourceBindInfo1 *BindInfo = |
| PSV.GetPSVResourceBindInfo1(ResourceIndex); |
| if (memcmp(&BI, BindInfo, sizeof(PSVResourceBindInfo1)) != 0) { |
| std::string ModuleStr = GetDump(BI); |
| std::string PartStr = GetDump(*BindInfo); |
| EmitMismatchError("ResourceBindInfo", PartStr, ModuleStr); |
| } |
| } else { |
| PSVResourceBindInfo0 *BindInfo = |
| PSV.GetPSVResourceBindInfo0(ResourceIndex); |
| if (memcmp(&BI, BindInfo, sizeof(PSVResourceBindInfo0)) != 0) { |
| std::string ModuleStr = GetDump(BI); |
| std::string PartStr = GetDump(*BindInfo); |
| EmitMismatchError("ResourceBindInfo", PartStr, ModuleStr); |
| } |
| } |
| ResourceIndex++; |
| } |
| } |
| |
| void PSVContentVerifier::VerifyResources(unsigned PSVVersion) { |
| UINT uCBuffers = DM.GetCBuffers().size(); |
| UINT uSamplers = DM.GetSamplers().size(); |
| UINT uSRVs = DM.GetSRVs().size(); |
| UINT uUAVs = DM.GetUAVs().size(); |
| unsigned ResourceCount = uCBuffers + uSamplers + uSRVs + uUAVs; |
| if (PSV.GetBindCount() != ResourceCount) { |
| EmitMismatchError("ResourceCount", std::to_string(PSV.GetBindCount()), |
| std::to_string(ResourceCount)); |
| return; |
| } |
| // Verify each resource table. |
| unsigned ResIndex = 0; |
| // CBV |
| VerifyResourceTable(DM.GetCBuffers(), ResIndex, PSVVersion); |
| // Sampler |
| VerifyResourceTable(DM.GetSamplers(), ResIndex, PSVVersion); |
| // SRV |
| VerifyResourceTable(DM.GetSRVs(), ResIndex, PSVVersion); |
| // UAV |
| VerifyResourceTable(DM.GetUAVs(), ResIndex, PSVVersion); |
| } |
| |
| void PSVContentVerifier::VerifyEntryProperties( |
| const ShaderModel *SM, PSVRuntimeInfo0 *PSV0, PSVRuntimeInfo1 *PSV1, |
| PSVRuntimeInfo2 *PSV2, PSVRuntimeInfo3 *PSV3, PSVRuntimeInfo4 *PSV4) { |
| PSVRuntimeInfo4 DMPSV; |
| memset(&DMPSV, 0, sizeof(PSVRuntimeInfo4)); |
| |
| hlsl::SetShaderProps((PSVRuntimeInfo0 *)&DMPSV, DM); |
| hlsl::SetShaderProps((PSVRuntimeInfo1 *)&DMPSV, DM); |
| hlsl::SetShaderProps((PSVRuntimeInfo2 *)&DMPSV, DM); |
| hlsl::SetShaderProps((PSVRuntimeInfo4 *)&DMPSV, DM); |
| if (PSV1) { |
| // Init things not set in InitPSVRuntimeInfo. |
| DMPSV.ShaderStage = static_cast<uint8_t>(SM->GetKind()); |
| DMPSV.SigInputElements = DM.GetInputSignature().GetElements().size(); |
| DMPSV.SigOutputElements = DM.GetOutputSignature().GetElements().size(); |
| DMPSV.SigPatchConstOrPrimElements = |
| DM.GetPatchConstOrPrimSignature().GetElements().size(); |
| // Set up ViewID and signature dependency info |
| DMPSV.UsesViewID = DM.m_ShaderFlags.GetViewID() ? true : false; |
| DMPSV.SigInputVectors = DM.GetInputSignature().NumVectorsUsed(0); |
| for (unsigned streamIndex = 0; streamIndex < 4; streamIndex++) |
| DMPSV.SigOutputVectors[streamIndex] = |
| DM.GetOutputSignature().NumVectorsUsed(streamIndex); |
| if (SM->IsHS() || SM->IsDS() || SM->IsMS()) |
| DMPSV.SigPatchConstOrPrimVectors = |
| DM.GetPatchConstOrPrimSignature().NumVectorsUsed(0); |
| } |
| bool Mismatched = false; |
| if (PSV2) |
| Mismatched = memcmp(PSV2, &DMPSV, sizeof(PSVRuntimeInfo2)) != 0; |
| else if (PSV1) |
| Mismatched = memcmp(PSV1, &DMPSV, sizeof(PSVRuntimeInfo1)) != 0; |
| else |
| Mismatched = memcmp(PSV0, &DMPSV, sizeof(PSVRuntimeInfo0)) != 0; |
| |
| if (PSV4 && |
| PSV4->NumBytesGroupSharedMemory != DMPSV.NumBytesGroupSharedMemory) |
| Mismatched = true; |
| |
| if (Mismatched) { |
| std::string Str; |
| raw_string_ostream OS(Str); |
| hlsl::PrintPSVRuntimeInfo(OS, &DMPSV, &DMPSV, &DMPSV, &DMPSV, &DMPSV, |
| static_cast<uint8_t>(SM->GetKind()), |
| DM.GetEntryFunctionName().c_str(), ""); |
| OS.flush(); |
| std::string Str1; |
| raw_string_ostream OS1(Str1); |
| PSV.PrintPSVRuntimeInfo(OS1, static_cast<uint8_t>(PSVShaderKind::Library), |
| ""); |
| OS1.flush(); |
| EmitMismatchError("PSVRuntimeInfo", Str, Str1); |
| } |
| } |
| |
| void PSVContentVerifier::Verify(unsigned ValMajor, unsigned ValMinor, |
| unsigned PSVVersion) { |
| PSVInitInfo PSVInfo(PSVVersion); |
| |
| if (PSV.GetBindCount() > 0 && |
| PSV.GetResourceBindInfoSize() != PSVInfo.ResourceBindInfoSize()) { |
| EmitMismatchError("ResourceBindInfoSize", |
| std::to_string(PSV.GetResourceBindInfoSize()), |
| std::to_string(PSVInfo.ResourceBindInfoSize())); |
| return; |
| } |
| VerifyResources(PSVVersion); |
| |
| PSVRuntimeInfo0 *PSV0 = PSV.GetPSVRuntimeInfo0(); |
| PSVRuntimeInfo1 *PSV1 = PSV.GetPSVRuntimeInfo1(); |
| PSVRuntimeInfo2 *PSV2 = PSV.GetPSVRuntimeInfo2(); |
| PSVRuntimeInfo3 *PSV3 = PSV.GetPSVRuntimeInfo3(); |
| PSVRuntimeInfo4 *PSV4 = PSV.GetPSVRuntimeInfo4(); |
| |
| const ShaderModel *SM = DM.GetShaderModel(); |
| VerifyEntryProperties(SM, PSV0, PSV1, PSV2, PSV3, PSV4); |
| if (PSVVersion > 0) { |
| if (((PSV.GetSigInputElements() + PSV.GetSigOutputElements() + |
| PSV.GetSigPatchConstOrPrimElements()) > 0) && |
| PSV.GetSignatureElementSize() != PSVInfo.SignatureElementSize()) { |
| EmitMismatchError("SignatureElementSize", |
| std::to_string(PSV.GetSignatureElementSize()), |
| std::to_string(PSVInfo.SignatureElementSize())); |
| return; |
| } |
| uint8_t ShaderStage = static_cast<uint8_t>(SM->GetKind()); |
| if (PSV1->ShaderStage != ShaderStage) { |
| EmitMismatchError("ShaderStage", std::to_string(PSV1->ShaderStage), |
| std::to_string(ShaderStage)); |
| return; |
| } |
| bool ViewIDUsed = PSV1->UsesViewID != 0; |
| if (ViewIDUsed != DM.m_ShaderFlags.GetViewID()) |
| EmitMismatchError("UsesViewID", std::to_string(PSV1->UsesViewID), |
| std::to_string(DM.m_ShaderFlags.GetViewID())); |
| |
| VerifySignatures(ValMajor, ValMinor); |
| |
| VerifyViewIDDependence(PSV1, PSVVersion); |
| } |
| // PSV2 only added NumThreadsX/Y/Z which verified in VerifyEntryProperties. |
| if (PSVVersion > 2) { |
| PSVRuntimeInfo3 *PSV3 = PSV.GetPSVRuntimeInfo3(); |
| if (!StrTableVerifier.MarkUse(PSV3->EntryFunctionName)) { |
| EmitInvalidError("EntryFunctionName"); |
| } else { |
| if (DM.GetEntryFunctionName() != PSV.GetEntryFunctionName()) |
| EmitMismatchError("EntryFunctionName", PSV.GetEntryFunctionName(), |
| DM.GetEntryFunctionName()); |
| } |
| } |
| |
| StrTableVerifier.Verify(ValCtx); |
| IndexTableVerifier.Verify(ValCtx); |
| |
| if (!PSVContentValid) |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, |
| {"Pipeline State Validation"}); |
| } |
| |
| } // namespace |
| |
| namespace hlsl { |
| |
| // DXIL Container Verification Functions |
| |
| static void VerifyBlobPartMatches(ValidationContext &ValCtx, LPCSTR pName, |
| DxilPartWriter *pWriter, const void *pData, |
| uint32_t Size) { |
| if (!pData && pWriter->size()) { |
| // No blob part, but writer says non-zero size is expected. |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMissing, {pName}); |
| return; |
| } |
| |
| // Compare sizes |
| if (pWriter->size() != Size) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, {pName}); |
| return; |
| } |
| |
| if (Size == 0) { |
| return; |
| } |
| |
| CComPtr<AbstractMemoryStream> pOutputStream; |
| IFT(CreateMemoryStream(DxcGetThreadMallocNoRef(), &pOutputStream)); |
| pOutputStream->Reserve(Size); |
| |
| pWriter->write(pOutputStream); |
| DXASSERT(pOutputStream->GetPtrSize() == Size, |
| "otherwise, DxilPartWriter misreported size"); |
| |
| if (memcmp(pData, pOutputStream->GetPtr(), Size)) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, {pName}); |
| return; |
| } |
| |
| return; |
| } |
| |
| static void VerifySignatureMatches(ValidationContext &ValCtx, |
| DXIL::SignatureKind SigKind, |
| const void *pSigData, uint32_t SigSize) { |
| // Generate corresponding signature from module and memcmp |
| |
| const char *pName = nullptr; |
| switch (SigKind) { |
| case hlsl::DXIL::SignatureKind::Input: |
| pName = "Program Input Signature"; |
| break; |
| case hlsl::DXIL::SignatureKind::Output: |
| pName = "Program Output Signature"; |
| break; |
| case hlsl::DXIL::SignatureKind::PatchConstOrPrim: |
| if (ValCtx.DxilMod.GetShaderModel()->GetKind() == DXIL::ShaderKind::Mesh) |
| pName = "Program Primitive Signature"; |
| else |
| pName = "Program Patch Constant Signature"; |
| break; |
| default: |
| break; |
| } |
| |
| unique_ptr<DxilPartWriter> pWriter( |
| NewProgramSignatureWriter(ValCtx.DxilMod, SigKind)); |
| VerifyBlobPartMatches(ValCtx, pName, pWriter.get(), pSigData, SigSize); |
| } |
| |
| bool VerifySignatureMatches(llvm::Module *pModule, DXIL::SignatureKind SigKind, |
| const void *pSigData, uint32_t SigSize) { |
| ValidationContext ValCtx(*pModule, nullptr, pModule->GetOrCreateDxilModule()); |
| VerifySignatureMatches(ValCtx, SigKind, pSigData, SigSize); |
| return !ValCtx.Failed; |
| } |
| |
| struct SimplePSV { |
| uint32_t PSVRuntimeInfoSize = 0; |
| uint32_t PSVNumResources = 0; |
| uint32_t PSVResourceBindInfoSize = 0; |
| uint32_t StringTableSize = 0; |
| const char *StringTable = nullptr; |
| uint32_t SemanticIndexTableEntries = 0; |
| const uint32_t *SemanticIndexTable = nullptr; |
| uint32_t PSVSignatureElementSize = 0; |
| const PSVRuntimeInfo1 *RuntimeInfo1 = nullptr; |
| bool IsValid = true; |
| SimplePSV(const void *pPSVData, uint32_t PSVSize) { |
| |
| #define INCREMENT_POS(Size) \ |
| Offset += Size; \ |
| if (Offset > PSVSize) { \ |
| IsValid = false; \ |
| return; \ |
| } |
| |
| uint32_t Offset = 0; |
| PSVRuntimeInfoSize = GetUint32AtOffset(pPSVData, 0); |
| INCREMENT_POS(4); |
| if (PSVRuntimeInfoSize >= sizeof(PSVRuntimeInfo1)) |
| RuntimeInfo1 = |
| (const PSVRuntimeInfo1 *)(GetPtrAtOffset(pPSVData, Offset)); |
| INCREMENT_POS(PSVRuntimeInfoSize); |
| |
| PSVNumResources = GetUint32AtOffset(pPSVData, Offset); |
| INCREMENT_POS(4); |
| if (PSVNumResources > 0) { |
| PSVResourceBindInfoSize = GetUint32AtOffset(pPSVData, Offset); |
| // Increase the offset for the resource bind info size. |
| INCREMENT_POS(4); |
| // Increase the offset for the resource bind info. |
| INCREMENT_POS(PSVNumResources * PSVResourceBindInfoSize); |
| } |
| if (RuntimeInfo1) { |
| StringTableSize = GetUint32AtOffset(pPSVData, Offset); |
| INCREMENT_POS(4); |
| // Make sure StringTableSize is aligned to 4 bytes. |
| if ((StringTableSize & 3) != 0) { |
| IsValid = false; |
| return; |
| } |
| if (StringTableSize) { |
| StringTable = GetPtrAtOffset(pPSVData, Offset); |
| INCREMENT_POS(StringTableSize); |
| } |
| SemanticIndexTableEntries = GetUint32AtOffset(pPSVData, Offset); |
| INCREMENT_POS(4); |
| if (SemanticIndexTableEntries) { |
| SemanticIndexTable = |
| (const uint32_t *)(GetPtrAtOffset(pPSVData, Offset)); |
| INCREMENT_POS(SemanticIndexTableEntries * 4); |
| } |
| if (RuntimeInfo1->SigInputElements || RuntimeInfo1->SigOutputElements || |
| RuntimeInfo1->SigPatchConstOrPrimElements) { |
| PSVSignatureElementSize = GetUint32AtOffset(pPSVData, Offset); |
| INCREMENT_POS(4); |
| uint32_t PSVNumSignatures = RuntimeInfo1->SigInputElements + |
| RuntimeInfo1->SigOutputElements + |
| RuntimeInfo1->SigPatchConstOrPrimElements; |
| INCREMENT_POS(PSVNumSignatures * PSVSignatureElementSize); |
| } |
| if (RuntimeInfo1->UsesViewID) { |
| for (unsigned i = 0; i < DXIL::kNumOutputStreams; i++) { |
| uint32_t SigOutputVectors = RuntimeInfo1->SigOutputVectors[i]; |
| if (SigOutputVectors == 0) |
| continue; |
| uint32_t MaskSizeInBytes = |
| sizeof(uint32_t) * |
| PSVComputeMaskDwordsFromVectors(SigOutputVectors); |
| INCREMENT_POS(MaskSizeInBytes); |
| } |
| if ((RuntimeInfo1->ShaderStage == (unsigned)DXIL::ShaderKind::Hull || |
| RuntimeInfo1->ShaderStage == (unsigned)DXIL::ShaderKind::Mesh) && |
| RuntimeInfo1->SigPatchConstOrPrimVectors) { |
| uint32_t MaskSizeInBytes = |
| sizeof(uint32_t) * PSVComputeMaskDwordsFromVectors( |
| RuntimeInfo1->SigPatchConstOrPrimVectors); |
| INCREMENT_POS(MaskSizeInBytes); |
| } |
| } |
| |
| for (unsigned i = 0; i < DXIL::kNumOutputStreams; i++) { |
| uint32_t SigOutputVectors = RuntimeInfo1->SigOutputVectors[i]; |
| if (SigOutputVectors == 0) |
| continue; |
| uint32_t TableSizeInBytes = |
| sizeof(uint32_t) * |
| PSVComputeInputOutputTableDwords(RuntimeInfo1->SigInputVectors, |
| SigOutputVectors); |
| INCREMENT_POS(TableSizeInBytes); |
| } |
| |
| if ((RuntimeInfo1->ShaderStage == (unsigned)DXIL::ShaderKind::Hull || |
| RuntimeInfo1->ShaderStage == (unsigned)DXIL::ShaderKind::Mesh) && |
| RuntimeInfo1->SigPatchConstOrPrimVectors && |
| RuntimeInfo1->SigInputVectors) { |
| uint32_t TableSizeInBytes = |
| sizeof(uint32_t) * PSVComputeInputOutputTableDwords( |
| RuntimeInfo1->SigInputVectors, |
| RuntimeInfo1->SigPatchConstOrPrimVectors); |
| INCREMENT_POS(TableSizeInBytes); |
| } |
| |
| if (RuntimeInfo1->ShaderStage == (unsigned)DXIL::ShaderKind::Domain && |
| RuntimeInfo1->SigOutputVectors[0] && |
| RuntimeInfo1->SigPatchConstOrPrimVectors) { |
| uint32_t TableSizeInBytes = |
| sizeof(uint32_t) * PSVComputeInputOutputTableDwords( |
| RuntimeInfo1->SigPatchConstOrPrimVectors, |
| RuntimeInfo1->SigOutputVectors[0]); |
| INCREMENT_POS(TableSizeInBytes); |
| } |
| } |
| IsValid = PSVSize == Offset; |
| #undef INCREMENT_POS |
| } |
| bool ValidatePSVInit(PSVInitInfo PSVInfo, ValidationContext &ValCtx) { |
| if (PSVRuntimeInfoSize != PSVInfo.RuntimeInfoSize()) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentMatches, |
| {"PSVRuntimeInfoSize", "PSV0", |
| std::to_string(PSVRuntimeInfoSize), |
| std::to_string(PSVInfo.RuntimeInfoSize())}); |
| return false; |
| } |
| if (PSVNumResources && |
| PSVResourceBindInfoSize != PSVInfo.ResourceBindInfoSize()) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentMatches, |
| {"PSVResourceBindInfoSize", "PSV0", |
| std::to_string(PSVResourceBindInfoSize), |
| std::to_string(PSVInfo.ResourceBindInfoSize())}); |
| return false; |
| } |
| if (RuntimeInfo1 && |
| (RuntimeInfo1->SigInputElements || RuntimeInfo1->SigOutputElements || |
| RuntimeInfo1->SigPatchConstOrPrimElements) && |
| PSVSignatureElementSize != PSVInfo.SignatureElementSize()) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentMatches, |
| {"PSVSignatureElementSize", "PSV0", |
| std::to_string(PSVSignatureElementSize), |
| std::to_string(PSVInfo.SignatureElementSize())}); |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| const char *GetPtrAtOffset(const void *BasePtr, uint32_t Offset) const { |
| return (const char *)BasePtr + Offset; |
| } |
| uint32_t GetUint32AtOffset(const void *BasePtr, uint32_t Offset) const { |
| return *(const uint32_t *)GetPtrAtOffset(BasePtr, Offset); |
| } |
| }; |
| |
| static void VerifyPSVMatches(ValidationContext &ValCtx, const void *pPSVData, |
| uint32_t PSVSize) { |
| // SimplePSV.IsValid indicates whether the part is well-formed so that we may |
| // proceed with more detailed validation. |
| SimplePSV SimplePSV(pPSVData, PSVSize); |
| if (!SimplePSV.IsValid) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentInvalid, |
| {"DxilContainer", "PSV0 part"}); |
| return; |
| } |
| // The PSVVersion determines the size of record structures that should be |
| // used when writing PSV0 data, and is based on the validator version in the |
| // module. |
| unsigned ValMajor, ValMinor; |
| ValCtx.DxilMod.GetValidatorVersion(ValMajor, ValMinor); |
| unsigned PSVVersion = hlsl::GetPSVVersion(ValMajor, ValMinor); |
| // PSVInfo is used to compute the expected record size of the PSV0 part of the |
| // container. It uses facts from the module. |
| PSVInitInfo PSVInfo(PSVVersion); |
| hlsl::SetupPSVInitInfo(PSVInfo, ValCtx.DxilMod); |
| // ValidatePSVInit checks that record sizes match expected for PSVVersion. |
| if (!SimplePSV.ValidatePSVInit(PSVInfo, ValCtx)) |
| return; |
| // Ensure that the string table data is null-terminated. |
| if (SimplePSV.StringTable && |
| SimplePSV.StringTable[SimplePSV.StringTableSize - 1] != '\0') { |
| ValCtx.EmitFormatError(ValidationRule::ContainerContentInvalid, |
| {"PSV part StringTable"}); |
| return; |
| } |
| |
| DxilPipelineStateValidation PSV; |
| if (!PSV.InitFromPSV0(pPSVData, PSVSize)) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, |
| {"Pipeline State Validation"}); |
| return; |
| } |
| |
| PSVContentVerifier Verifier(PSV, ValCtx.DxilMod, ValCtx); |
| Verifier.Verify(ValMajor, ValMinor, PSVVersion); |
| } |
| |
| static void VerifyFeatureInfoMatches(ValidationContext &ValCtx, |
| const void *pFeatureInfoData, |
| uint32_t FeatureInfoSize) { |
| // generate Feature Info data from module and memcmp |
| unique_ptr<DxilPartWriter> pWriter(NewFeatureInfoWriter(ValCtx.DxilMod)); |
| VerifyBlobPartMatches(ValCtx, "Feature Info", pWriter.get(), pFeatureInfoData, |
| FeatureInfoSize); |
| } |
| |
| // return true if the pBlob is a valid, well-formed CompilerVersion part, false |
| // otherwise |
| bool ValidateCompilerVersionPart(const void *pBlobPtr, UINT blobSize) { |
| // The hlsl::DxilCompilerVersion struct is always 16 bytes. (2 2-byte |
| // uint16's, 3 4-byte uint32's) The blob size should absolutely never be less |
| // than 16 bytes. |
| if (blobSize < sizeof(hlsl::DxilCompilerVersion)) { |
| return false; |
| } |
| |
| const hlsl::DxilCompilerVersion *pDCV = |
| (const hlsl::DxilCompilerVersion *)pBlobPtr; |
| if (pDCV->VersionStringListSizeInBytes == 0) { |
| // No version strings, just make sure there is no extra space. |
| return blobSize == sizeof(hlsl::DxilCompilerVersion); |
| } |
| |
| // after this point, we know VersionStringListSizeInBytes >= 1, because it is |
| // a UINT |
| |
| UINT EndOfVersionStringIndex = |
| sizeof(hlsl::DxilCompilerVersion) + pDCV->VersionStringListSizeInBytes; |
| // Make sure that the buffer size is large enough to contain both the DCV |
| // struct and the version string but not any larger than necessary |
| if (PSVALIGN4(EndOfVersionStringIndex) != blobSize) { |
| return false; |
| } |
| |
| const char *VersionStringsListData = |
| (const char *)pBlobPtr + sizeof(hlsl::DxilCompilerVersion); |
| UINT VersionStringListSizeInBytes = pDCV->VersionStringListSizeInBytes; |
| |
| // now make sure that any pad bytes that were added are null-terminators. |
| for (UINT i = VersionStringListSizeInBytes; |
| i < blobSize - sizeof(hlsl::DxilCompilerVersion); i++) { |
| if (VersionStringsListData[i] != '\0') { |
| return false; |
| } |
| } |
| |
| // Now, version string validation |
| // first, the final byte of the string should always be null-terminator so |
| // that the string ends |
| if (VersionStringsListData[VersionStringListSizeInBytes - 1] != '\0') { |
| return false; |
| } |
| |
| // construct the first string |
| // data format for VersionString can be see in the definition for the |
| // DxilCompilerVersion struct. summary: 2 strings that each end with the null |
| // terminator, and [0-3] null terminators after the final null terminator |
| StringRef firstStr(VersionStringsListData); |
| |
| // if the second string exists, attempt to construct it. |
| if (VersionStringListSizeInBytes > (firstStr.size() + 1)) { |
| StringRef secondStr(VersionStringsListData + firstStr.size() + 1); |
| |
| // the VersionStringListSizeInBytes member should be exactly equal to the |
| // two string lengths, plus the 2 null terminator bytes. |
| if (VersionStringListSizeInBytes != |
| firstStr.size() + secondStr.size() + 2) { |
| return false; |
| } |
| } else { |
| // the VersionStringListSizeInBytes member should be exactly equal to the |
| // first string length, plus the 1 null terminator byte. |
| if (VersionStringListSizeInBytes != firstStr.size() + 1) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static void VerifyRDATMatches(ValidationContext &ValCtx, const void *pRDATData, |
| uint32_t RDATSize) { |
| const char *PartName = "Runtime Data (RDAT)"; |
| RDAT::DxilRuntimeData rdat(pRDATData, RDATSize); |
| if (!rdat.Validate()) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, {PartName}); |
| return; |
| } |
| |
| // If DxilModule subobjects already loaded, validate these against the RDAT |
| // blob, otherwise, load subobject into DxilModule to generate reference RDAT. |
| if (!ValCtx.DxilMod.GetSubobjects()) { |
| auto table = rdat.GetSubobjectTable(); |
| if (table && table.Count() > 0) { |
| ValCtx.DxilMod.ResetSubobjects(new DxilSubobjects()); |
| if (!LoadSubobjectsFromRDAT(*ValCtx.DxilMod.GetSubobjects(), rdat)) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, |
| {PartName}); |
| return; |
| } |
| } |
| } |
| |
| unique_ptr<DxilPartWriter> pWriter(NewRDATWriter(ValCtx.DxilMod)); |
| VerifyBlobPartMatches(ValCtx, PartName, pWriter.get(), pRDATData, RDATSize); |
| } |
| |
| bool VerifyRDATMatches(llvm::Module *pModule, const void *pRDATData, |
| uint32_t RDATSize) { |
| ValidationContext ValCtx(*pModule, nullptr, pModule->GetOrCreateDxilModule()); |
| VerifyRDATMatches(ValCtx, pRDATData, RDATSize); |
| return !ValCtx.Failed; |
| } |
| |
| bool VerifyFeatureInfoMatches(llvm::Module *pModule, |
| const void *pFeatureInfoData, |
| uint32_t FeatureInfoSize) { |
| ValidationContext ValCtx(*pModule, nullptr, pModule->GetOrCreateDxilModule()); |
| VerifyFeatureInfoMatches(ValCtx, pFeatureInfoData, FeatureInfoSize); |
| return !ValCtx.Failed; |
| } |
| |
| HRESULT ValidateDxilContainerParts(llvm::Module *pModule, |
| llvm::Module *pDebugModule, |
| const DxilContainerHeader *pContainer, |
| uint32_t ContainerSize) { |
| |
| DXASSERT_NOMSG(pModule); |
| if (!pContainer || !IsValidDxilContainer(pContainer, ContainerSize)) { |
| return DXC_E_CONTAINER_INVALID; |
| } |
| |
| DxilModule *pDxilModule = DxilModule::TryGetDxilModule(pModule); |
| if (!pDxilModule) { |
| return DXC_E_IR_VERIFICATION_FAILED; |
| } |
| |
| ValidationContext ValCtx(*pModule, pDebugModule, *pDxilModule); |
| |
| DXIL::ShaderKind ShaderKind = pDxilModule->GetShaderModel()->GetKind(); |
| bool bTessOrMesh = ShaderKind == DXIL::ShaderKind::Hull || |
| ShaderKind == DXIL::ShaderKind::Domain || |
| ShaderKind == DXIL::ShaderKind::Mesh; |
| |
| std::unordered_set<uint32_t> FourCCFound; |
| const DxilPartHeader *pRootSignaturePart = nullptr; |
| const DxilPartHeader *pPSVPart = nullptr; |
| |
| for (auto it = begin(pContainer), itEnd = end(pContainer); it != itEnd; |
| ++it) { |
| const DxilPartHeader *pPart = *it; |
| |
| char szFourCC[5]; |
| PartKindToCharArray(pPart->PartFourCC, szFourCC); |
| if (FourCCFound.find(pPart->PartFourCC) != FourCCFound.end()) { |
| // Two parts with same FourCC found |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartRepeated, {szFourCC}); |
| continue; |
| } |
| FourCCFound.insert(pPart->PartFourCC); |
| |
| switch (pPart->PartFourCC) { |
| case DFCC_InputSignature: |
| if (ValCtx.isLibProfile) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } else { |
| VerifySignatureMatches(ValCtx, DXIL::SignatureKind::Input, |
| GetDxilPartData(pPart), pPart->PartSize); |
| } |
| break; |
| case DFCC_OutputSignature: |
| if (ValCtx.isLibProfile) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } else { |
| VerifySignatureMatches(ValCtx, DXIL::SignatureKind::Output, |
| GetDxilPartData(pPart), pPart->PartSize); |
| } |
| break; |
| case DFCC_PatchConstantSignature: |
| if (ValCtx.isLibProfile) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } else { |
| if (bTessOrMesh) { |
| VerifySignatureMatches(ValCtx, DXIL::SignatureKind::PatchConstOrPrim, |
| GetDxilPartData(pPart), pPart->PartSize); |
| } else { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMatches, |
| {"Program Patch Constant Signature"}); |
| } |
| } |
| break; |
| case DFCC_FeatureInfo: |
| VerifyFeatureInfoMatches(ValCtx, GetDxilPartData(pPart), pPart->PartSize); |
| break; |
| case DFCC_CompilerVersion: |
| // This blob is either a PDB, or a library profile |
| if (ValCtx.isLibProfile) { |
| if (!ValidateCompilerVersionPart((void *)GetDxilPartData(pPart), |
| pPart->PartSize)) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } |
| } else { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } |
| break; |
| |
| case DFCC_RootSignature: |
| pRootSignaturePart = pPart; |
| if (ValCtx.isLibProfile) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } |
| break; |
| case DFCC_PipelineStateValidation: |
| pPSVPart = pPart; |
| if (ValCtx.isLibProfile) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } else { |
| VerifyPSVMatches(ValCtx, GetDxilPartData(pPart), pPart->PartSize); |
| } |
| break; |
| |
| // Skip these |
| case DFCC_ResourceDef: |
| case DFCC_ShaderStatistics: |
| case DFCC_PrivateData: |
| break; |
| case DFCC_DXIL: |
| case DFCC_ShaderDebugInfoDXIL: { |
| const DxilProgramHeader *pProgramHeader = |
| reinterpret_cast<const DxilProgramHeader *>(GetDxilPartData(pPart)); |
| if (!pProgramHeader) |
| continue; |
| |
| int PV = pProgramHeader->ProgramVersion; |
| int major = (PV >> 4) & 0xF; // Extract the major version (next 4 bits) |
| int minor = PV & 0xF; // Extract the minor version (lowest 4 bits) |
| |
| int moduleMajor = pDxilModule->GetShaderModel()->GetMajor(); |
| int moduleMinor = pDxilModule->GetShaderModel()->GetMinor(); |
| if (moduleMajor != major || moduleMinor != minor) { |
| ValCtx.EmitFormatError(ValidationRule::SmProgramVersion, |
| {std::to_string(major), std::to_string(minor), |
| std::to_string(moduleMajor), |
| std::to_string(moduleMinor)}); |
| return DXC_E_INCORRECT_PROGRAM_VERSION; |
| } |
| continue; |
| } |
| case DFCC_ShaderDebugName: |
| continue; |
| |
| case DFCC_ShaderHash: |
| if (pPart->PartSize != sizeof(DxilShaderHash)) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } |
| break; |
| |
| // Runtime Data (RDAT) for libraries |
| case DFCC_RuntimeData: |
| if (ValCtx.isLibProfile) { |
| // TODO: validate without exact binary comparison of serialized data |
| // - support earlier versions |
| // - verify no newer record versions than known here (size no larger |
| // than newest version) |
| // - verify all data makes sense and matches expectations based on |
| // module |
| VerifyRDATMatches(ValCtx, GetDxilPartData(pPart), pPart->PartSize); |
| } else { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, |
| {szFourCC}); |
| } |
| break; |
| |
| case DFCC_Container: |
| default: |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartInvalid, {szFourCC}); |
| break; |
| } |
| } |
| |
| // Verify required parts found |
| if (ValCtx.isLibProfile) { |
| if (FourCCFound.find(DFCC_RuntimeData) == FourCCFound.end()) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMissing, |
| {"Runtime Data (RDAT)"}); |
| } |
| } else { |
| if (FourCCFound.find(DFCC_InputSignature) == FourCCFound.end()) { |
| VerifySignatureMatches(ValCtx, DXIL::SignatureKind::Input, nullptr, 0); |
| } |
| if (FourCCFound.find(DFCC_OutputSignature) == FourCCFound.end()) { |
| VerifySignatureMatches(ValCtx, DXIL::SignatureKind::Output, nullptr, 0); |
| } |
| if (bTessOrMesh && |
| FourCCFound.find(DFCC_PatchConstantSignature) == FourCCFound.end() && |
| pDxilModule->GetPatchConstOrPrimSignature().GetElements().size()) { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMissing, |
| {"Program Patch Constant Signature"}); |
| } |
| if (FourCCFound.find(DFCC_FeatureInfo) == FourCCFound.end()) { |
| // Could be optional, but RS1 runtime doesn't handle this case properly. |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMissing, |
| {"Feature Info"}); |
| } |
| |
| // Validate Root Signature |
| if (pPSVPart) { |
| if (pRootSignaturePart) { |
| std::string diagStr; |
| raw_string_ostream DiagStream(diagStr); |
| try { |
| RootSignatureHandle RS; |
| RS.LoadSerialized( |
| (const uint8_t *)GetDxilPartData(pRootSignaturePart), |
| pRootSignaturePart->PartSize); |
| RS.Deserialize(); |
| IFTBOOL(VerifyRootSignatureWithShaderPSV( |
| RS.GetDesc(), pDxilModule->GetShaderModel()->GetKind(), |
| GetDxilPartData(pPSVPart), pPSVPart->PartSize, |
| DiagStream), |
| DXC_E_INCORRECT_ROOT_SIGNATURE); |
| } catch (...) { |
| ValCtx.EmitError(ValidationRule::ContainerRootSignatureIncompatible); |
| emitDxilDiag(pModule->getContext(), DiagStream.str().c_str()); |
| } |
| } |
| } else { |
| ValCtx.EmitFormatError(ValidationRule::ContainerPartMissing, |
| {"Pipeline State Validation"}); |
| } |
| } |
| |
| if (ValCtx.Failed) { |
| return DXC_E_MALFORMED_CONTAINER; |
| } |
| return S_OK; |
| } |
| |
| static HRESULT FindDxilPart(const void *pContainerBytes, uint32_t ContainerSize, |
| DxilFourCC FourCC, const DxilPartHeader **ppPart) { |
| |
| const DxilContainerHeader *pContainer = |
| IsDxilContainerLike(pContainerBytes, ContainerSize); |
| |
| if (!pContainer) { |
| IFR(DXC_E_CONTAINER_INVALID); |
| } |
| if (!IsValidDxilContainer(pContainer, ContainerSize)) { |
| IFR(DXC_E_CONTAINER_INVALID); |
| } |
| |
| DxilPartIterator it = |
| std::find_if(begin(pContainer), end(pContainer), DxilPartIsType(FourCC)); |
| if (it == end(pContainer)) { |
| IFR(DXC_E_CONTAINER_MISSING_DXIL); |
| } |
| |
| const DxilProgramHeader *pProgramHeader = |
| reinterpret_cast<const DxilProgramHeader *>(GetDxilPartData(*it)); |
| if (!IsValidDxilProgramHeader(pProgramHeader, (*it)->PartSize)) { |
| IFR(DXC_E_CONTAINER_INVALID); |
| } |
| |
| *ppPart = *it; |
| return S_OK; |
| } |
| |
| HRESULT ValidateLoadModule(const char *pIL, uint32_t ILLength, |
| unique_ptr<llvm::Module> &pModule, LLVMContext &Ctx, |
| llvm::raw_ostream &DiagStream, unsigned bLazyLoad) { |
| |
| llvm::DiagnosticPrinterRawOStream DiagPrinter(DiagStream); |
| PrintDiagnosticContext DiagContext(DiagPrinter); |
| DiagRestore DR(Ctx, &DiagContext); |
| |
| std::unique_ptr<llvm::MemoryBuffer> pBitcodeBuf; |
| pBitcodeBuf.reset(llvm::MemoryBuffer::getMemBuffer( |
| llvm::StringRef(pIL, ILLength), "", false) |
| .release()); |
| |
| ErrorOr<std::unique_ptr<Module>> loadedModuleResult = |
| bLazyLoad == 0 |
| ? llvm::parseBitcodeFile(pBitcodeBuf->getMemBufferRef(), Ctx, nullptr, |
| true /*Track Bitstream*/) |
| : llvm::getLazyBitcodeModule(std::move(pBitcodeBuf), Ctx, nullptr, |
| false, true /*Track Bitstream*/); |
| |
| // DXIL disallows some LLVM bitcode constructs, like unaccounted-for |
| // sub-blocks. These appear as warnings, which the validator should reject. |
| if (DiagContext.HasErrors() || DiagContext.HasWarnings() || |
| loadedModuleResult.getError()) |
| return DXC_E_IR_VERIFICATION_FAILED; |
| |
| pModule = std::move(loadedModuleResult.get()); |
| return S_OK; |
| } |
| |
| HRESULT ValidateDxilBitcode(const char *pIL, uint32_t ILLength, |
| llvm::raw_ostream &DiagStream) { |
| |
| LLVMContext Ctx; |
| std::unique_ptr<llvm::Module> pModule; |
| |
| llvm::DiagnosticPrinterRawOStream DiagPrinter(DiagStream); |
| PrintDiagnosticContext DiagContext(DiagPrinter); |
| Ctx.setDiagnosticHandler(PrintDiagnosticContext::PrintDiagnosticHandler, |
| &DiagContext, true); |
| |
| HRESULT hr; |
| if (FAILED(hr = ValidateLoadModule(pIL, ILLength, pModule, Ctx, DiagStream, |
| /*bLazyLoad*/ false))) |
| return hr; |
| |
| if (FAILED(hr = ValidateDxilModule(pModule.get(), nullptr))) |
| return hr; |
| |
| DxilModule &dxilModule = pModule->GetDxilModule(); |
| auto &SerializedRootSig = dxilModule.GetSerializedRootSignature(); |
| if (!SerializedRootSig.empty()) { |
| unique_ptr<DxilPartWriter> pWriter(NewPSVWriter(dxilModule)); |
| DXASSERT_NOMSG(pWriter->size()); |
| CComPtr<AbstractMemoryStream> pOutputStream; |
| IFT(CreateMemoryStream(DxcGetThreadMallocNoRef(), &pOutputStream)); |
| pOutputStream->Reserve(pWriter->size()); |
| pWriter->write(pOutputStream); |
| DxilVersionedRootSignature desc; |
| try { |
| DeserializeRootSignature(SerializedRootSig.data(), |
| SerializedRootSig.size(), desc.get_address_of()); |
| if (!desc.get()) { |
| return DXC_E_INCORRECT_ROOT_SIGNATURE; |
| } |
| IFTBOOL(VerifyRootSignatureWithShaderPSV( |
| desc.get(), dxilModule.GetShaderModel()->GetKind(), |
| pOutputStream->GetPtr(), pWriter->size(), DiagStream), |
| DXC_E_INCORRECT_ROOT_SIGNATURE); |
| } catch (...) { |
| return DXC_E_INCORRECT_ROOT_SIGNATURE; |
| } |
| } |
| |
| if (DiagContext.HasErrors() || DiagContext.HasWarnings()) { |
| return DXC_E_IR_VERIFICATION_FAILED; |
| } |
| |
| return S_OK; |
| } |
| |
| static HRESULT ValidateLoadModuleFromContainer( |
| const void *pContainer, uint32_t ContainerSize, |
| std::unique_ptr<llvm::Module> &pModule, |
| std::unique_ptr<llvm::Module> &pDebugModule, llvm::LLVMContext &Ctx, |
| LLVMContext &DbgCtx, llvm::raw_ostream &DiagStream, unsigned bLazyLoad) { |
| llvm::DiagnosticPrinterRawOStream DiagPrinter(DiagStream); |
| PrintDiagnosticContext DiagContext(DiagPrinter); |
| DiagRestore DR(Ctx, &DiagContext); |
| DiagRestore DR2(DbgCtx, &DiagContext); |
| |
| const DxilPartHeader *pPart = nullptr; |
| IFR(FindDxilPart(pContainer, ContainerSize, DFCC_DXIL, &pPart)); |
| |
| const char *pIL = nullptr; |
| uint32_t ILLength = 0; |
| GetDxilProgramBitcode( |
| reinterpret_cast<const DxilProgramHeader *>(GetDxilPartData(pPart)), &pIL, |
| &ILLength); |
| |
| IFR(ValidateLoadModule(pIL, ILLength, pModule, Ctx, DiagStream, bLazyLoad)); |
| |
| HRESULT hr; |
| const DxilPartHeader *pDbgPart = nullptr; |
| if (FAILED(hr = FindDxilPart(pContainer, ContainerSize, |
| DFCC_ShaderDebugInfoDXIL, &pDbgPart)) && |
| hr != DXC_E_CONTAINER_MISSING_DXIL) { |
| return hr; |
| } |
| |
| if (pDbgPart) { |
| GetDxilProgramBitcode( |
| reinterpret_cast<const DxilProgramHeader *>(GetDxilPartData(pDbgPart)), |
| &pIL, &ILLength); |
| if (FAILED(hr = ValidateLoadModule(pIL, ILLength, pDebugModule, DbgCtx, |
| DiagStream, bLazyLoad))) { |
| return hr; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT ValidateLoadModuleFromContainer( |
| const void *pContainer, uint32_t ContainerSize, |
| std::unique_ptr<llvm::Module> &pModule, |
| std::unique_ptr<llvm::Module> &pDebugModule, llvm::LLVMContext &Ctx, |
| llvm::LLVMContext &DbgCtx, llvm::raw_ostream &DiagStream) { |
| return ValidateLoadModuleFromContainer(pContainer, ContainerSize, pModule, |
| pDebugModule, Ctx, DbgCtx, DiagStream, |
| /*bLazyLoad*/ false); |
| } |
| // Lazy loads module from container, validating load, but not module. |
| HRESULT ValidateLoadModuleFromContainerLazy( |
| const void *pContainer, uint32_t ContainerSize, |
| std::unique_ptr<llvm::Module> &pModule, |
| std::unique_ptr<llvm::Module> &pDebugModule, llvm::LLVMContext &Ctx, |
| llvm::LLVMContext &DbgCtx, llvm::raw_ostream &DiagStream) { |
| return ValidateLoadModuleFromContainer(pContainer, ContainerSize, pModule, |
| pDebugModule, Ctx, DbgCtx, DiagStream, |
| /*bLazyLoad*/ true); |
| } |
| |
| HRESULT ValidateDxilContainer(const void *pContainer, uint32_t ContainerSize, |
| llvm::Module *pDebugModule, |
| llvm::raw_ostream &DiagStream) { |
| LLVMContext Ctx, DbgCtx; |
| std::unique_ptr<llvm::Module> pModule, pDebugModuleInContainer; |
| |
| llvm::DiagnosticPrinterRawOStream DiagPrinter(DiagStream); |
| PrintDiagnosticContext DiagContext(DiagPrinter); |
| Ctx.setDiagnosticHandler(PrintDiagnosticContext::PrintDiagnosticHandler, |
| &DiagContext, true); |
| DbgCtx.setDiagnosticHandler(PrintDiagnosticContext::PrintDiagnosticHandler, |
| &DiagContext, true); |
| |
| DiagRestore DR(pDebugModule, &DiagContext); |
| |
| IFR(ValidateLoadModuleFromContainer(pContainer, ContainerSize, pModule, |
| pDebugModuleInContainer, Ctx, DbgCtx, |
| DiagStream)); |
| |
| if (pDebugModuleInContainer) |
| pDebugModule = pDebugModuleInContainer.get(); |
| |
| // Validate DXIL Module |
| IFR(ValidateDxilModule(pModule.get(), pDebugModule)); |
| |
| if (DiagContext.HasErrors() || DiagContext.HasWarnings()) { |
| return DXC_E_IR_VERIFICATION_FAILED; |
| } |
| |
| return ValidateDxilContainerParts( |
| pModule.get(), pDebugModule, |
| IsDxilContainerLike(pContainer, ContainerSize), ContainerSize); |
| } |
| |
| HRESULT ValidateDxilContainer(const void *pContainer, uint32_t ContainerSize, |
| llvm::raw_ostream &DiagStream) { |
| return ValidateDxilContainer(pContainer, ContainerSize, nullptr, DiagStream); |
| } |
| } // namespace hlsl |