//===- DXContainer.h - DXContainer file implementation ----------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file declares the DXContainerFile class, which implements the ObjectFile // interface for DXContainer files. // // //===----------------------------------------------------------------------===// #ifndef LLVM_OBJECT_DXCONTAINER_H #define LLVM_OBJECT_DXCONTAINER_H #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/BinaryFormat/DXContainer.h" #include "llvm/Object/Error.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Endian.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBufferRef.h" #include "llvm/TargetParser/Triple.h" #include #include #include #include namespace llvm { namespace object { namespace detail { template std::enable_if_t::value, void> swapBytes(T &value) { sys::swapByteOrder(value); } template std::enable_if_t::value, void> swapBytes(T &value) { value.swapBytes(); } } // namespace detail // This class provides a view into the underlying resource array. The Resource // data is little-endian encoded and may not be properly aligned to read // directly from. The dereference operator creates a copy of the data and byte // swaps it as appropriate. template struct ViewArray { StringRef Data; uint32_t Stride = sizeof(T); // size of each element in the list. ViewArray() = default; ViewArray(StringRef D, size_t S) : Data(D), Stride(S) {} using value_type = T; static constexpr uint32_t MaxStride() { return static_cast(sizeof(value_type)); } struct iterator { StringRef Data; uint32_t Stride; // size of each element in the list. const char *Current; iterator(const ViewArray &A, const char *C) : Data(A.Data), Stride(A.Stride), Current(C) {} iterator(const iterator &) = default; value_type operator*() { // Explicitly zero the structure so that unused fields are zeroed. It is // up to the user to know if the fields are used by verifying the PSV // version. value_type Val; std::memset(&Val, 0, sizeof(value_type)); if (Current >= Data.end()) return Val; memcpy(static_cast(&Val), Current, std::min(Stride, MaxStride())); if (sys::IsBigEndianHost) detail::swapBytes(Val); return Val; } iterator operator++() { if (Current < Data.end()) Current += Stride; return *this; } iterator operator++(int) { iterator Tmp = *this; ++*this; return Tmp; } iterator operator--() { if (Current > Data.begin()) Current -= Stride; return *this; } iterator operator--(int) { iterator Tmp = *this; --*this; return Tmp; } bool operator==(const iterator I) { return I.Current == Current; } bool operator!=(const iterator I) { return !(*this == I); } }; iterator begin() const { return iterator(*this, Data.begin()); } iterator end() const { return iterator(*this, Data.end()); } size_t size() const { return Data.size() / Stride; } bool isEmpty() const { return Data.empty(); } }; namespace DirectX { struct RootParameterView { const dxbc::RTS0::v1::RootParameterHeader &Header; StringRef ParamData; RootParameterView(const dxbc::RTS0::v1::RootParameterHeader &H, StringRef P) : Header(H), ParamData(P) {} template Expected readParameter() { T Struct; if (sizeof(T) != ParamData.size()) return make_error( "Reading structure out of file bounds", object_error::parse_failed); memcpy(&Struct, ParamData.data(), sizeof(T)); // DXContainer is always little endian if (sys::IsBigEndianHost) Struct.swapBytes(); return Struct; } }; struct RootConstantView : RootParameterView { static bool classof(const RootParameterView *V) { return V->Header.ParameterType == (uint32_t)dxbc::RootParameterType::Constants32Bit; } llvm::Expected read() { return readParameter(); } }; struct RootDescriptorView : RootParameterView { static bool classof(const RootParameterView *V) { return (V->Header.ParameterType == llvm::to_underlying(dxbc::RootParameterType::CBV) || V->Header.ParameterType == llvm::to_underlying(dxbc::RootParameterType::SRV) || V->Header.ParameterType == llvm::to_underlying(dxbc::RootParameterType::UAV)); } llvm::Expected read(uint32_t Version) { if (Version == 1) { auto Descriptor = readParameter(); if (Error E = Descriptor.takeError()) return E; return dxbc::RTS0::v2::RootDescriptor(*Descriptor); } if (Version != 2) return make_error("Invalid Root Signature version: " + Twine(Version), object_error::parse_failed); return readParameter(); } }; template struct DescriptorTable { uint32_t NumRanges; uint32_t RangesOffset; ViewArray Ranges; typename ViewArray::iterator begin() const { return Ranges.begin(); } typename ViewArray::iterator end() const { return Ranges.end(); } }; struct DescriptorTableView : RootParameterView { static bool classof(const RootParameterView *V) { return (V->Header.ParameterType == llvm::to_underlying(dxbc::RootParameterType::DescriptorTable)); } // Define a type alias to access the template parameter from inside classof template llvm::Expected> read() { const char *Current = ParamData.begin(); DescriptorTable Table; Table.NumRanges = support::endian::read(Current); Current += sizeof(uint32_t); Table.RangesOffset = support::endian::read(Current); Current += sizeof(uint32_t); Table.Ranges.Data = ParamData.substr(2 * sizeof(uint32_t), Table.NumRanges * Table.Ranges.Stride); return Table; } }; static Error parseFailed(const Twine &Msg) { return make_error(Msg.str(), object_error::parse_failed); } class RootSignature { private: uint32_t Version; uint32_t NumParameters; uint32_t RootParametersOffset; uint32_t NumStaticSamplers; uint32_t StaticSamplersOffset; uint32_t Flags; ViewArray ParametersHeaders; StringRef PartData; ViewArray StaticSamplers; using param_header_iterator = ViewArray::iterator; using samplers_iterator = ViewArray::iterator; public: RootSignature(StringRef PD) : PartData(PD) {} LLVM_ABI Error parse(); uint32_t getVersion() const { return Version; } uint32_t getNumParameters() const { return NumParameters; } uint32_t getRootParametersOffset() const { return RootParametersOffset; } uint32_t getNumStaticSamplers() const { return NumStaticSamplers; } uint32_t getStaticSamplersOffset() const { return StaticSamplersOffset; } uint32_t getNumRootParameters() const { return ParametersHeaders.size(); } llvm::iterator_range param_headers() const { return ParametersHeaders; } llvm::iterator_range samplers() const { return StaticSamplers; } uint32_t getFlags() const { return Flags; } llvm::Expected getParameter(const dxbc::RTS0::v1::RootParameterHeader &Header) const { size_t DataSize; size_t EndOfSectionByte = getNumStaticSamplers() == 0 ? PartData.size() : getStaticSamplersOffset(); if (!dxbc::isValidParameterType(Header.ParameterType)) return parseFailed("invalid parameter type"); switch (static_cast(Header.ParameterType)) { case dxbc::RootParameterType::Constants32Bit: DataSize = sizeof(dxbc::RTS0::v1::RootConstants); break; case dxbc::RootParameterType::CBV: case dxbc::RootParameterType::SRV: case dxbc::RootParameterType::UAV: if (Version == 1) DataSize = sizeof(dxbc::RTS0::v1::RootDescriptor); else DataSize = sizeof(dxbc::RTS0::v2::RootDescriptor); break; case dxbc::RootParameterType::DescriptorTable: if (Header.ParameterOffset + sizeof(uint32_t) > EndOfSectionByte) return parseFailed("Reading structure out of file bounds"); uint32_t NumRanges = support::endian::read( PartData.begin() + Header.ParameterOffset); if (Version == 1) DataSize = sizeof(dxbc::RTS0::v1::DescriptorRange) * NumRanges; else DataSize = sizeof(dxbc::RTS0::v2::DescriptorRange) * NumRanges; // 4 bytes for the number of ranges in table and // 4 bytes for the ranges offset DataSize += 2 * sizeof(uint32_t); break; } if (Header.ParameterOffset + DataSize > EndOfSectionByte) return parseFailed("Reading structure out of file bounds"); StringRef Buff = PartData.substr(Header.ParameterOffset, DataSize); RootParameterView View = RootParameterView(Header, Buff); return View; } }; class PSVRuntimeInfo { using ResourceArray = ViewArray; using SigElementArray = ViewArray; StringRef Data; uint32_t Size; using InfoStruct = std::variant; InfoStruct BasicInfo; ResourceArray Resources; StringRef StringTable; SmallVector SemanticIndexTable; SigElementArray SigInputElements; SigElementArray SigOutputElements; SigElementArray SigPatchOrPrimElements; std::array, 4> OutputVectorMasks; ViewArray PatchOrPrimMasks; std::array, 4> InputOutputMap; ViewArray InputPatchMap; ViewArray PatchOutputMap; public: PSVRuntimeInfo(StringRef D) : Data(D), Size(0) {} // Parsing depends on the shader kind LLVM_ABI Error parse(uint16_t ShaderKind); uint32_t getSize() const { return Size; } uint32_t getResourceCount() const { return Resources.size(); } ResourceArray getResources() const { return Resources; } uint32_t getVersion() const { return Size >= sizeof(dxbc::PSV::v3::RuntimeInfo) ? 3 : (Size >= sizeof(dxbc::PSV::v2::RuntimeInfo) ? 2 : (Size >= sizeof(dxbc::PSV::v1::RuntimeInfo)) ? 1 : 0); } uint32_t getResourceStride() const { return Resources.Stride; } const InfoStruct &getInfo() const { return BasicInfo; } template const T *getInfoAs() const { if (const auto *P = std::get_if(&BasicInfo)) return static_cast(P); if (std::is_same::value) return nullptr; if (const auto *P = std::get_if(&BasicInfo)) return static_cast(P); if (std::is_same::value) return nullptr; if (const auto *P = std::get_if(&BasicInfo)) return static_cast(P); if (std::is_same::value) return nullptr; if (const auto *P = std::get_if(&BasicInfo)) return static_cast(P); return nullptr; } StringRef getStringTable() const { return StringTable; } ArrayRef getSemanticIndexTable() const { return SemanticIndexTable; } LLVM_ABI uint8_t getSigInputCount() const; LLVM_ABI uint8_t getSigOutputCount() const; LLVM_ABI uint8_t getSigPatchOrPrimCount() const; SigElementArray getSigInputElements() const { return SigInputElements; } SigElementArray getSigOutputElements() const { return SigOutputElements; } SigElementArray getSigPatchOrPrimElements() const { return SigPatchOrPrimElements; } ViewArray getOutputVectorMasks(size_t Idx) const { assert(Idx < 4); return OutputVectorMasks[Idx]; } ViewArray getPatchOrPrimMasks() const { return PatchOrPrimMasks; } ViewArray getInputOutputMap(size_t Idx) const { assert(Idx < 4); return InputOutputMap[Idx]; } ViewArray getInputPatchMap() const { return InputPatchMap; } ViewArray getPatchOutputMap() const { return PatchOutputMap; } uint32_t getSigElementStride() const { return SigInputElements.Stride; } bool usesViewID() const { if (const auto *P = getInfoAs()) return P->UsesViewID != 0; return false; } uint8_t getInputVectorCount() const { if (const auto *P = getInfoAs()) return P->SigInputVectors; return 0; } ArrayRef getOutputVectorCounts() const { if (const auto *P = getInfoAs()) return ArrayRef(P->SigOutputVectors); return ArrayRef(); } uint8_t getPatchConstOrPrimVectorCount() const { if (const auto *P = getInfoAs()) return P->GeomData.SigPatchConstOrPrimVectors; return 0; } }; class Signature { ViewArray Parameters; uint32_t StringTableOffset; StringRef StringTable; public: ViewArray::iterator begin() const { return Parameters.begin(); } ViewArray::iterator end() const { return Parameters.end(); } StringRef getName(uint32_t Offset) const { assert(Offset >= StringTableOffset && Offset < StringTableOffset + StringTable.size() && "Offset out of range."); // Name offsets are from the start of the signature data, not from the start // of the string table. The header encodes the start offset of the sting // table, so we convert the offset here. uint32_t TableOffset = Offset - StringTableOffset; return StringTable.slice(TableOffset, StringTable.find('\0', TableOffset)); } bool isEmpty() const { return Parameters.isEmpty(); } LLVM_ABI Error initialize(StringRef Part); }; } // namespace DirectX class DXContainer { public: using DXILData = std::pair; private: DXContainer(MemoryBufferRef O); MemoryBufferRef Data; dxbc::Header Header; SmallVector PartOffsets; std::optional DXIL; std::optional ShaderFeatureFlags; std::optional Hash; std::optional PSVInfo; std::optional RootSignature; DirectX::Signature InputSignature; DirectX::Signature OutputSignature; DirectX::Signature PatchConstantSignature; Error parseHeader(); Error parsePartOffsets(); Error parseDXILHeader(StringRef Part); Error parseShaderFeatureFlags(StringRef Part); Error parseHash(StringRef Part); Error parseRootSignature(StringRef Part); Error parsePSVInfo(StringRef Part); Error parseSignature(StringRef Part, DirectX::Signature &Array); friend class PartIterator; public: // The PartIterator is a wrapper around the iterator for the PartOffsets // member of the DXContainer. It contains a refernce to the container, and the // current iterator value, as well as storage for a parsed part header. class PartIterator { const DXContainer &Container; SmallVectorImpl::const_iterator OffsetIt; struct PartData { dxbc::PartHeader Part; uint32_t Offset; StringRef Data; } IteratorState; friend class DXContainer; friend class DXContainerObjectFile; PartIterator(const DXContainer &C, SmallVectorImpl::const_iterator It) : Container(C), OffsetIt(It) { if (OffsetIt == Container.PartOffsets.end()) updateIteratorImpl(Container.PartOffsets.back()); else updateIterator(); } // Updates the iterator's state data. This results in copying the part // header into the iterator and handling any required byte swapping. This is // called when incrementing or decrementing the iterator. void updateIterator() { if (OffsetIt != Container.PartOffsets.end()) updateIteratorImpl(*OffsetIt); } // Implementation for updating the iterator state based on a specified // offest. LLVM_ABI void updateIteratorImpl(const uint32_t Offset); public: PartIterator &operator++() { if (OffsetIt == Container.PartOffsets.end()) return *this; ++OffsetIt; updateIterator(); return *this; } PartIterator operator++(int) { PartIterator Tmp = *this; ++(*this); return Tmp; } bool operator==(const PartIterator &RHS) const { return OffsetIt == RHS.OffsetIt; } bool operator!=(const PartIterator &RHS) const { return OffsetIt != RHS.OffsetIt; } const PartData &operator*() { return IteratorState; } const PartData *operator->() { return &IteratorState; } }; PartIterator begin() const { return PartIterator(*this, PartOffsets.begin()); } PartIterator end() const { return PartIterator(*this, PartOffsets.end()); } StringRef getData() const { return Data.getBuffer(); } LLVM_ABI static Expected create(MemoryBufferRef Object); const dxbc::Header &getHeader() const { return Header; } const std::optional &getDXIL() const { return DXIL; } std::optional getShaderFeatureFlags() const { return ShaderFeatureFlags; } std::optional getShaderHash() const { return Hash; } std::optional getRootSignature() const { return RootSignature; } const std::optional &getPSVInfo() const { return PSVInfo; }; const DirectX::Signature &getInputSignature() const { return InputSignature; } const DirectX::Signature &getOutputSignature() const { return OutputSignature; } const DirectX::Signature &getPatchConstantSignature() const { return PatchConstantSignature; } }; class LLVM_ABI DXContainerObjectFile : public ObjectFile { private: friend class ObjectFile; DXContainer Container; using PartData = DXContainer::PartIterator::PartData; llvm::SmallVector Parts; using PartIterator = llvm::SmallVector::iterator; DXContainerObjectFile(DXContainer C) : ObjectFile(ID_DXContainer, MemoryBufferRef(C.getData(), "")), Container(C) { for (auto &P : C) Parts.push_back(P); } public: const DXContainer &getDXContainer() const { return Container; } static bool classof(const Binary *v) { return v->isDXContainer(); } const dxbc::Header &getHeader() const { return Container.getHeader(); } Expected getSymbolName(DataRefImpl) const override; Expected getSymbolAddress(DataRefImpl Symb) const override; uint64_t getSymbolValueImpl(DataRefImpl Symb) const override; uint64_t getCommonSymbolSizeImpl(DataRefImpl Symb) const override; Expected getSymbolType(DataRefImpl Symb) const override; Expected getSymbolSection(DataRefImpl Symb) const override; void moveSectionNext(DataRefImpl &Sec) const override; Expected getSectionName(DataRefImpl Sec) const override; uint64_t getSectionAddress(DataRefImpl Sec) const override; uint64_t getSectionIndex(DataRefImpl Sec) const override; uint64_t getSectionSize(DataRefImpl Sec) const override; Expected> getSectionContents(DataRefImpl Sec) const override; uint64_t getSectionAlignment(DataRefImpl Sec) const override; bool isSectionCompressed(DataRefImpl Sec) const override; bool isSectionText(DataRefImpl Sec) const override; bool isSectionData(DataRefImpl Sec) const override; bool isSectionBSS(DataRefImpl Sec) const override; bool isSectionVirtual(DataRefImpl Sec) const override; relocation_iterator section_rel_begin(DataRefImpl Sec) const override; relocation_iterator section_rel_end(DataRefImpl Sec) const override; void moveRelocationNext(DataRefImpl &Rel) const override; uint64_t getRelocationOffset(DataRefImpl Rel) const override; symbol_iterator getRelocationSymbol(DataRefImpl Rel) const override; uint64_t getRelocationType(DataRefImpl Rel) const override; void getRelocationTypeName(DataRefImpl Rel, SmallVectorImpl &Result) const override; section_iterator section_begin() const override; section_iterator section_end() const override; uint8_t getBytesInAddress() const override; StringRef getFileFormatName() const override; Triple::ArchType getArch() const override; Expected getFeatures() const override; void moveSymbolNext(DataRefImpl &Symb) const override {} Error printSymbolName(raw_ostream &OS, DataRefImpl Symb) const override; Expected getSymbolFlags(DataRefImpl Symb) const override; basic_symbol_iterator symbol_begin() const override { return basic_symbol_iterator(SymbolRef()); } basic_symbol_iterator symbol_end() const override { return basic_symbol_iterator(SymbolRef()); } bool is64Bit() const override { return false; } bool isRelocatableObject() const override { return false; } }; } // namespace object } // namespace llvm #endif // LLVM_OBJECT_DXCONTAINER_H