//===- 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
