Files
clang-p2996/llvm/lib/ObjectYAML/DXContainerEmitter.cpp
Chris B 9f87522b12 [DX] Add support for program signatures (#67346)
For DirectX, program signatures are encoded into three different binary
sections depending on if the signature is for inputs, outputs, or
patches. All three signature types use the same data structure encoding
so they can share a lot of logic.

This patch adds support for reading and writing program signature data
as both yaml and binary data.

Fixes #57743 and #57744
2023-10-05 10:33:15 -05:00

296 lines
10 KiB
C++

//===- DXContainerEmitter.cpp - Convert YAML to a DXContainer -------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Binary emitter for yaml to DXContainer binary
///
//===----------------------------------------------------------------------===//
#include "llvm/BinaryFormat/DXContainer.h"
#include "llvm/MC/DXContainerPSVInfo.h"
#include "llvm/ObjectYAML/ObjectYAML.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
using namespace llvm;
namespace {
class DXContainerWriter {
public:
DXContainerWriter(DXContainerYAML::Object &ObjectFile)
: ObjectFile(ObjectFile) {}
Error write(raw_ostream &OS);
private:
DXContainerYAML::Object &ObjectFile;
Error computePartOffsets();
Error validatePartOffsets();
Error validateSize(uint32_t Computed);
void writeHeader(raw_ostream &OS);
void writeParts(raw_ostream &OS);
};
} // namespace
Error DXContainerWriter::validateSize(uint32_t Computed) {
if (!ObjectFile.Header.FileSize)
ObjectFile.Header.FileSize = Computed;
else if (*ObjectFile.Header.FileSize < Computed)
return createStringError(errc::result_out_of_range,
"File size specified is too small.");
return Error::success();
}
Error DXContainerWriter::validatePartOffsets() {
if (ObjectFile.Parts.size() != ObjectFile.Header.PartOffsets->size())
return createStringError(
errc::invalid_argument,
"Mismatch between number of parts and part offsets.");
uint32_t RollingOffset =
sizeof(dxbc::Header) + (ObjectFile.Header.PartCount * sizeof(uint32_t));
for (auto I : llvm::zip(ObjectFile.Parts, *ObjectFile.Header.PartOffsets)) {
if (RollingOffset > std::get<1>(I))
return createStringError(errc::invalid_argument,
"Offset mismatch, not enough space for data.");
RollingOffset =
std::get<1>(I) + sizeof(dxbc::PartHeader) + std::get<0>(I).Size;
}
if (Error Err = validateSize(RollingOffset))
return Err;
return Error::success();
}
Error DXContainerWriter::computePartOffsets() {
if (ObjectFile.Header.PartOffsets)
return validatePartOffsets();
uint32_t RollingOffset =
sizeof(dxbc::Header) + (ObjectFile.Header.PartCount * sizeof(uint32_t));
ObjectFile.Header.PartOffsets = std::vector<uint32_t>();
for (const auto &Part : ObjectFile.Parts) {
ObjectFile.Header.PartOffsets->push_back(RollingOffset);
RollingOffset += sizeof(dxbc::PartHeader) + Part.Size;
}
if (Error Err = validateSize(RollingOffset))
return Err;
return Error::success();
}
void DXContainerWriter::writeHeader(raw_ostream &OS) {
dxbc::Header Header;
memcpy(Header.Magic, "DXBC", 4);
memcpy(Header.FileHash.Digest, ObjectFile.Header.Hash.data(), 16);
Header.Version.Major = ObjectFile.Header.Version.Major;
Header.Version.Minor = ObjectFile.Header.Version.Minor;
Header.FileSize = *ObjectFile.Header.FileSize;
Header.PartCount = ObjectFile.Parts.size();
if (sys::IsBigEndianHost)
Header.swapBytes();
OS.write(reinterpret_cast<char *>(&Header), sizeof(Header));
SmallVector<uint32_t> Offsets(ObjectFile.Header.PartOffsets->begin(),
ObjectFile.Header.PartOffsets->end());
if (sys::IsBigEndianHost)
for (auto &O : Offsets)
sys::swapByteOrder(O);
OS.write(reinterpret_cast<char *>(Offsets.data()),
Offsets.size() * sizeof(uint32_t));
}
void DXContainerWriter::writeParts(raw_ostream &OS) {
uint32_t RollingOffset =
sizeof(dxbc::Header) + (ObjectFile.Header.PartCount * sizeof(uint32_t));
for (auto I : llvm::zip(ObjectFile.Parts, *ObjectFile.Header.PartOffsets)) {
if (RollingOffset < std::get<1>(I)) {
uint32_t PadBytes = std::get<1>(I) - RollingOffset;
OS.write_zeros(PadBytes);
}
DXContainerYAML::Part P = std::get<0>(I);
RollingOffset = std::get<1>(I) + sizeof(dxbc::PartHeader);
uint32_t PartSize = P.Size;
OS.write(P.Name.c_str(), 4);
if (sys::IsBigEndianHost)
sys::swapByteOrder(P.Size);
OS.write(reinterpret_cast<const char *>(&P.Size), sizeof(uint32_t));
dxbc::PartType PT = dxbc::parsePartType(P.Name);
uint64_t DataStart = OS.tell();
switch (PT) {
case dxbc::PartType::DXIL: {
if (!P.Program)
continue;
dxbc::ProgramHeader Header;
Header.MajorVersion = P.Program->MajorVersion;
Header.MinorVersion = P.Program->MinorVersion;
Header.Unused = 0;
Header.ShaderKind = P.Program->ShaderKind;
memcpy(Header.Bitcode.Magic, "DXIL", 4);
Header.Bitcode.MajorVersion = P.Program->DXILMajorVersion;
Header.Bitcode.MinorVersion = P.Program->DXILMinorVersion;
Header.Bitcode.Unused = 0;
// Compute the optional fields if needed...
if (P.Program->DXILOffset)
Header.Bitcode.Offset = *P.Program->DXILOffset;
else
Header.Bitcode.Offset = sizeof(dxbc::BitcodeHeader);
if (P.Program->DXILSize)
Header.Bitcode.Size = *P.Program->DXILSize;
else
Header.Bitcode.Size = P.Program->DXIL ? P.Program->DXIL->size() : 0;
if (P.Program->Size)
Header.Size = *P.Program->Size;
else
Header.Size = sizeof(dxbc::ProgramHeader) + Header.Bitcode.Size;
uint32_t BitcodeOffset = Header.Bitcode.Offset;
if (sys::IsBigEndianHost)
Header.swapBytes();
OS.write(reinterpret_cast<const char *>(&Header),
sizeof(dxbc::ProgramHeader));
if (P.Program->DXIL) {
if (BitcodeOffset > sizeof(dxbc::BitcodeHeader)) {
uint32_t PadBytes = BitcodeOffset - sizeof(dxbc::BitcodeHeader);
OS.write_zeros(PadBytes);
}
OS.write(reinterpret_cast<char *>(P.Program->DXIL->data()),
P.Program->DXIL->size());
}
break;
}
case dxbc::PartType::SFI0: {
// If we don't have any flags we can continue here and the data will be
// zeroed out.
if (!P.Flags.has_value())
continue;
uint64_t Flags = P.Flags->getEncodedFlags();
if (sys::IsBigEndianHost)
sys::swapByteOrder(Flags);
OS.write(reinterpret_cast<char *>(&Flags), sizeof(uint64_t));
break;
}
case dxbc::PartType::HASH: {
if (!P.Hash.has_value())
continue;
dxbc::ShaderHash Hash = {0, {0}};
if (P.Hash->IncludesSource)
Hash.Flags |= static_cast<uint32_t>(dxbc::HashFlags::IncludesSource);
memcpy(&Hash.Digest[0], &P.Hash->Digest[0], 16);
if (sys::IsBigEndianHost)
Hash.swapBytes();
OS.write(reinterpret_cast<char *>(&Hash), sizeof(dxbc::ShaderHash));
break;
}
case dxbc::PartType::PSV0: {
if (!P.Info.has_value())
continue;
mcdxbc::PSVRuntimeInfo PSV;
memcpy(&PSV.BaseData, &P.Info->Info, sizeof(dxbc::PSV::v2::RuntimeInfo));
PSV.Resources = P.Info->Resources;
for (auto El : P.Info->SigInputElements)
PSV.InputElements.push_back(mcdxbc::PSVSignatureElement{
El.Name, El.Indices, El.StartRow, El.Cols, El.StartCol,
El.Allocated, El.Kind, El.Type, El.Mode, El.DynamicMask,
El.Stream});
for (auto El : P.Info->SigOutputElements)
PSV.OutputElements.push_back(mcdxbc::PSVSignatureElement{
El.Name, El.Indices, El.StartRow, El.Cols, El.StartCol,
El.Allocated, El.Kind, El.Type, El.Mode, El.DynamicMask,
El.Stream});
for (auto El : P.Info->SigPatchOrPrimElements)
PSV.PatchOrPrimElements.push_back(mcdxbc::PSVSignatureElement{
El.Name, El.Indices, El.StartRow, El.Cols, El.StartCol,
El.Allocated, El.Kind, El.Type, El.Mode, El.DynamicMask,
El.Stream});
static_assert(PSV.OutputVectorMasks.size() == PSV.InputOutputMap.size());
for (unsigned I = 0; I < PSV.OutputVectorMasks.size(); ++I) {
PSV.OutputVectorMasks[I].insert(PSV.OutputVectorMasks[I].begin(),
P.Info->OutputVectorMasks[I].begin(),
P.Info->OutputVectorMasks[I].end());
PSV.InputOutputMap[I].insert(PSV.InputOutputMap[I].begin(),
P.Info->InputOutputMap[I].begin(),
P.Info->InputOutputMap[I].end());
}
PSV.PatchOrPrimMasks.insert(PSV.PatchOrPrimMasks.begin(),
P.Info->PatchOrPrimMasks.begin(),
P.Info->PatchOrPrimMasks.end());
PSV.InputPatchMap.insert(PSV.InputPatchMap.begin(),
P.Info->InputPatchMap.begin(),
P.Info->InputPatchMap.end());
PSV.PatchOutputMap.insert(PSV.PatchOutputMap.begin(),
P.Info->PatchOutputMap.begin(),
P.Info->PatchOutputMap.end());
PSV.finalize(static_cast<Triple::EnvironmentType>(
Triple::Pixel + P.Info->Info.ShaderStage));
PSV.write(OS, P.Info->Version);
break;
}
case dxbc::PartType::ISG1:
case dxbc::PartType::OSG1:
case dxbc::PartType::PSG1: {
mcdxbc::Signature Sig;
if (P.Signature.has_value()) {
for (const auto &Param : P.Signature->Parameters) {
Sig.addParam(Param.Stream, Param.Name, Param.Index, Param.SystemValue,
Param.CompType, Param.Register, Param.Mask,
Param.ExclusiveMask, Param.MinPrecision);
}
}
Sig.write(OS);
break;
}
case dxbc::PartType::Unknown:
break; // Skip any handling for unrecognized parts.
}
uint64_t BytesWritten = OS.tell() - DataStart;
RollingOffset += BytesWritten;
if (BytesWritten < PartSize)
OS.write_zeros(PartSize - BytesWritten);
RollingOffset += PartSize;
}
}
Error DXContainerWriter::write(raw_ostream &OS) {
if (Error Err = computePartOffsets())
return Err;
writeHeader(OS);
writeParts(OS);
return Error::success();
}
namespace llvm {
namespace yaml {
bool yaml2dxcontainer(DXContainerYAML::Object &Doc, raw_ostream &Out,
ErrorHandler EH) {
DXContainerWriter Writer(Doc);
if (Error Err = Writer.write(Out)) {
handleAllErrors(std::move(Err),
[&](const ErrorInfoBase &Err) { EH(Err.message()); });
return false;
}
return true;
}
} // namespace yaml
} // namespace llvm