///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// DxilModule.h                                                              //
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
// This file is distributed under the University of Illinois Open Source     //
// License. See LICENSE.TXT for details.                                     //
//                                                                           //
// The main class to work with DXIL, similar to LLVM module.                 //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#pragma once

#include "dxc/DXIL/DxilCBuffer.h"
#include "dxc/DXIL/DxilConstants.h"
#include "dxc/DXIL/DxilMetadataHelper.h"
#include "dxc/DXIL/DxilResource.h"
#include "dxc/DXIL/DxilSampler.h"
#include "dxc/DXIL/DxilShaderFlags.h"
#include "dxc/DXIL/DxilSignature.h"
#include "dxc/DXIL/DxilSubobject.h"
#include "dxc/DXIL/DxilTypeSystem.h"

#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace llvm {
class LLVMContext;
class Module;
class Function;
class Instruction;
class MDTuple;
class MDOperand;
class DebugInfoFinder;
} // namespace llvm

namespace hlsl {

class ShaderModel;
class OP;
struct DxilFunctionProps;

class DxilEntryProps;

using DxilEntryPropsMap =
    std::unordered_map<const llvm::Function *, std::unique_ptr<DxilEntryProps>>;

/// Use this class to manipulate DXIL of a shader.
class DxilModule {
public:
  DxilModule(llvm::Module *pModule);
  ~DxilModule();

  // Subsystems.
  llvm::LLVMContext &GetCtx() const;
  llvm::Module *GetModule() const;
  OP *GetOP() const;
  void SetShaderModel(const ShaderModel *pSM, bool bUseMinPrecision = true);
  const ShaderModel *GetShaderModel() const;
  void GetDxilVersion(unsigned &DxilMajor, unsigned &DxilMinor) const;
  void SetValidatorVersion(unsigned ValMajor, unsigned ValMinor);
  bool UpgradeValidatorVersion(unsigned ValMajor, unsigned ValMinor);
  void GetValidatorVersion(unsigned &ValMajor, unsigned &ValMinor) const;
  void SetForceZeroStoreLifetimes(bool ForceZeroStoreLifetimes);
  bool GetForceZeroStoreLifetimes() const;

  // Return true on success, requires valid shader model and CollectShaderFlags
  // to have been set
  bool GetMinValidatorVersion(unsigned &ValMajor, unsigned &ValMinor) const;
  // Update validator version to minimum if higher than current (ex: after
  // CollectShaderFlags)
  bool UpgradeToMinValidatorVersion();

  // Entry functions.
  llvm::Function *GetEntryFunction();
  const llvm::Function *GetEntryFunction() const;
  void SetEntryFunction(llvm::Function *pEntryFunc);
  const std::string &GetEntryFunctionName() const;
  void SetEntryFunctionName(const std::string &name);
  llvm::Function *GetPatchConstantFunction();
  const llvm::Function *GetPatchConstantFunction() const;
  void SetPatchConstantFunction(llvm::Function *pFunc);
  bool IsEntryOrPatchConstantFunction(const llvm::Function *pFunc) const;
  llvm::SmallVector<llvm::Function *, 64> GetExportedFunctions();

  // Flags.
  unsigned GetGlobalFlags() const;
  void CollectShaderFlagsForModule();

  // Resources.
  unsigned AddCBuffer(std::unique_ptr<DxilCBuffer> pCB);
  DxilCBuffer &GetCBuffer(unsigned idx);
  const DxilCBuffer &GetCBuffer(unsigned idx) const;
  const std::vector<std::unique_ptr<DxilCBuffer>> &GetCBuffers() const;

  unsigned AddSampler(std::unique_ptr<DxilSampler> pSampler);
  DxilSampler &GetSampler(unsigned idx);
  const DxilSampler &GetSampler(unsigned idx) const;
  const std::vector<std::unique_ptr<DxilSampler>> &GetSamplers() const;

  unsigned AddSRV(std::unique_ptr<DxilResource> pSRV);
  DxilResource &GetSRV(unsigned idx);
  const DxilResource &GetSRV(unsigned idx) const;
  const std::vector<std::unique_ptr<DxilResource>> &GetSRVs() const;

  unsigned AddUAV(std::unique_ptr<DxilResource> pUAV);
  DxilResource &GetUAV(unsigned idx);
  const DxilResource &GetUAV(unsigned idx) const;
  const std::vector<std::unique_ptr<DxilResource>> &GetUAVs() const;

  void RemoveUnusedResources();
  void RemoveResourcesWithUnusedSymbols();
  void RemoveFunction(llvm::Function *F);

  bool RenameResourcesWithPrefix(const std::string &prefix);
  bool RenameResourceGlobalsWithBinding(bool bKeepName = true);

  // Signatures.
  DxilSignature &GetInputSignature();
  const DxilSignature &GetInputSignature() const;
  DxilSignature &GetOutputSignature();
  const DxilSignature &GetOutputSignature() const;
  DxilSignature &GetPatchConstOrPrimSignature();
  const DxilSignature &GetPatchConstOrPrimSignature() const;
  const std::vector<uint8_t> &GetSerializedRootSignature() const;
  std::vector<uint8_t> &GetSerializedRootSignature();

  bool HasDxilEntrySignature(const llvm::Function *F) const;
  DxilEntrySignature &GetDxilEntrySignature(const llvm::Function *F);
  // Move DxilEntryProps of F to NewF.
  void ReplaceDxilEntryProps(llvm::Function *F, llvm::Function *NewF);
  // Clone DxilEntryProps of F to NewF.
  void CloneDxilEntryProps(llvm::Function *F, llvm::Function *NewF);
  bool HasDxilEntryProps(const llvm::Function *F) const;
  DxilEntryProps &GetDxilEntryProps(const llvm::Function *F);
  const DxilEntryProps &GetDxilEntryProps(const llvm::Function *F) const;

  // DxilFunctionProps.
  bool HasDxilFunctionProps(const llvm::Function *F) const;
  DxilFunctionProps &GetDxilFunctionProps(const llvm::Function *F);
  const DxilFunctionProps &GetDxilFunctionProps(const llvm::Function *F) const;

  // Move DxilFunctionProps of F to NewF.
  void SetPatchConstantFunctionForHS(llvm::Function *hullShaderFunc,
                                     llvm::Function *patchConstantFunc);
  bool IsGraphicsShader(const llvm::Function *F) const; // vs,hs,ds,gs,ps
  bool IsPatchConstantShader(const llvm::Function *F) const;
  bool IsComputeShader(const llvm::Function *F) const;

  // Is an entry function that uses input/output signature conventions?
  // Includes: vs/hs/ds/gs/ps/cs as well as the patch constant function.
  bool IsEntryThatUsesSignatures(const llvm::Function *F) const;
  // Is F an entry?
  // Includes: IsEntryThatUsesSignatures and all ray tracing shaders.
  bool IsEntry(const llvm::Function *F) const;

  // Remove Root Signature from module metadata, return true if changed
  bool StripRootSignatureFromMetadata();
  // Remove Subobjects from module metadata, return true if changed
  bool StripSubobjectsFromMetadata();
  // Update validator version metadata to current setting
  void UpdateValidatorVersionMetadata();

  // DXIL type system.
  DxilTypeSystem &GetTypeSystem();
  const DxilTypeSystem &GetTypeSystem() const;

  /// Emit llvm.used array to make sure that optimizations do not remove
  /// unreferenced globals.
  void EmitLLVMUsed();
  std::vector<llvm::GlobalVariable *> &GetLLVMUsed();
  void ClearLLVMUsed();

  // ViewId state.
  std::vector<unsigned> &GetSerializedViewIdState();
  const std::vector<unsigned> &GetSerializedViewIdState() const;

  // DXIL metadata manipulation.
  /// Clear all DXIL data that exists in in-memory form.
  static void ClearDxilMetadata(llvm::Module &M);
  /// Serialize DXIL in-memory form to metadata form.
  void EmitDxilMetadata();
  /// Update resource metadata.
  /// Note: this method not update Metadata for ViewIdState.
  void ReEmitDxilResources();
  /// Deserialize DXIL metadata form into in-memory form.
  void LoadDxilMetadata();
  /// Return true if non-fatal metadata error was detected.
  bool HasMetadataErrors();

  void EmitDxilCounters();
  void LoadDxilCounters(DxilCounters &counters) const;

  /// Check if a Named meta data node is known by dxil module.
  static bool IsKnownNamedMetaData(llvm::NamedMDNode &Node);

  // Reset functions used to transfer ownership.
  void ResetEntrySignature(DxilEntrySignature *pValue);
  void ResetSerializedRootSignature(std::vector<uint8_t> &Value);
  void ResetTypeSystem(DxilTypeSystem *pValue);
  void ResetOP(hlsl::OP *hlslOP);
  void ResetEntryPropsMap(DxilEntryPropsMap &&PropMap);

  bool StripReflection();
  void StripDebugRelatedCode();
  void RemoveUnusedTypeAnnotations();

  // Copy resource reflection back to this module's resources.
  void RestoreResourceReflection(const DxilModule &SourceDM);

  // Helper to remove dx.* metadata with source and compile options.
  // If the parameter `bReplaceWithDummyData` is true, the named metadata
  // are replaced with valid empty data that satisfy tools.
  void StripShaderSourcesAndCompileOptions(bool bReplaceWithDummyData = false);
  llvm::DebugInfoFinder &GetOrCreateDebugInfoFinder();

  static DxilModule *TryGetDxilModule(llvm::Module *pModule);

  // Helpers for working with precise.

  // Return true if the instruction should be considered precise.
  //
  // An instruction can be marked precise in the following ways:
  //
  // 1. Global refactoring is disabled.
  // 2. The instruction has a precise metadata annotation.
  // 3. The instruction has precise fast math flags set.
  //
  bool IsPrecise(const llvm::Instruction *inst) const;

  // Check if the instruction has fast math flags configured to indicate
  // the instruction is precise.
  static bool HasPreciseFastMathFlags(const llvm::Instruction *inst);

  // Set fast math flags configured to indicate the instruction is precise.
  static void SetPreciseFastMathFlags(llvm::Instruction *inst);

  // True if fast math flags are preserved across serialize/deserialize.
  static bool PreservesFastMathFlags(const llvm::Instruction *inst);

public:
  ShaderFlags m_ShaderFlags;
  void CollectShaderFlagsForModule(ShaderFlags &Flags);

  // Check if DxilModule contains multi component UAV Loads.
  // This funciton must be called after unused resources are removed from
  // DxilModule
  bool ModuleHasMulticomponentUAVLoads();

  // Compute/Mesh/Amplification shader.
  void SetNumThreads(unsigned x, unsigned y, unsigned z);
  unsigned GetNumThreads(unsigned idx) const;

  // Compute shader
  DxilWaveSize &GetWaveSize();
  const DxilWaveSize &GetWaveSize() const;

  // Geometry shader.
  DXIL::InputPrimitive GetInputPrimitive() const;
  void SetInputPrimitive(DXIL::InputPrimitive IP);
  unsigned GetMaxVertexCount() const;
  void SetMaxVertexCount(unsigned Count);
  DXIL::PrimitiveTopology GetStreamPrimitiveTopology() const;
  void SetStreamPrimitiveTopology(DXIL::PrimitiveTopology Topology);
  bool HasMultipleOutputStreams() const;
  unsigned GetOutputStream() const;
  unsigned GetGSInstanceCount() const;
  void SetGSInstanceCount(unsigned Count);
  bool IsStreamActive(unsigned Stream) const;
  void SetStreamActive(unsigned Stream, bool bActive);
  void SetActiveStreamMask(unsigned Mask);
  unsigned GetActiveStreamMask() const;

  // Language options
  // UseMinPrecision must be set at SetShaderModel time.
  bool GetUseMinPrecision() const;
  void SetDisableOptimization(bool disableOptimization);
  bool GetDisableOptimization() const;
  void SetAllResourcesBound(bool resourcesBound);
  bool GetAllResourcesBound() const;
  void SetResMayAlias(bool resMayAlias);
  bool GetResMayAlias() const;

  // Intermediate options that do not make it to DXIL
  void SetLegacyResourceReservation(bool legacyResourceReservation);
  bool GetLegacyResourceReservation() const;
  void ClearIntermediateOptions();

  // Hull and Domain shaders.
  unsigned GetInputControlPointCount() const;
  void SetInputControlPointCount(unsigned NumICPs);
  DXIL::TessellatorDomain GetTessellatorDomain() const;
  void SetTessellatorDomain(DXIL::TessellatorDomain TessDomain);

  // Hull shader.
  unsigned GetOutputControlPointCount() const;
  void SetOutputControlPointCount(unsigned NumOCPs);
  DXIL::TessellatorPartitioning GetTessellatorPartitioning() const;
  void
  SetTessellatorPartitioning(DXIL::TessellatorPartitioning TessPartitioning);
  DXIL::TessellatorOutputPrimitive GetTessellatorOutputPrimitive() const;
  void SetTessellatorOutputPrimitive(
      DXIL::TessellatorOutputPrimitive TessOutputPrimitive);
  float GetMaxTessellationFactor() const;
  void SetMaxTessellationFactor(float MaxTessellationFactor);

  // Mesh shader
  unsigned GetMaxOutputVertices() const;
  void SetMaxOutputVertices(unsigned NumOVs);
  unsigned GetMaxOutputPrimitives() const;
  void SetMaxOutputPrimitives(unsigned NumOPs);
  DXIL::MeshOutputTopology GetMeshOutputTopology() const;
  void SetMeshOutputTopology(DXIL::MeshOutputTopology MeshOutputTopology);
  unsigned GetPayloadSizeInBytes() const;
  void SetPayloadSizeInBytes(unsigned Size);

  // AutoBindingSpace also enables automatic binding for libraries if set.
  // UINT_MAX == unset
  void SetAutoBindingSpace(uint32_t Space);
  uint32_t GetAutoBindingSpace() const;

  void SetShaderProperties(DxilFunctionProps *props);

  DxilSubobjects *GetSubobjects();
  const DxilSubobjects *GetSubobjects() const;
  DxilSubobjects *ReleaseSubobjects();
  void ResetSubobjects(DxilSubobjects *subobjects);

private:
  // Signatures.
  std::vector<uint8_t> m_SerializedRootSignature;

  // Shader resources.
  std::vector<std::unique_ptr<DxilResource>> m_SRVs;
  std::vector<std::unique_ptr<DxilResource>> m_UAVs;
  std::vector<std::unique_ptr<DxilCBuffer>> m_CBuffers;
  std::vector<std::unique_ptr<DxilSampler>> m_Samplers;

  // Geometry shader.
  DXIL::PrimitiveTopology m_StreamPrimitiveTopology =
      DXIL::PrimitiveTopology::Undefined;
  unsigned m_ActiveStreamMask = 0;

  enum IntermediateFlags : uint32_t {
    LegacyResourceReservation = 1 << 0,
  };

  llvm::LLVMContext &m_Ctx;
  llvm::Module *m_pModule = nullptr;
  llvm::Function *m_pEntryFunc = nullptr;
  std::string m_EntryName = "";
  std::unique_ptr<DxilMDHelper> m_pMDHelper;
  std::unique_ptr<llvm::DebugInfoFinder> m_pDebugInfoFinder;
  const ShaderModel *m_pSM = nullptr;
  unsigned m_DxilMajor = DXIL::kDxilMajor;
  unsigned m_DxilMinor = DXIL::kDxilMinor;
  unsigned m_ValMajor = 1;
  unsigned m_ValMinor = 0;
  bool m_ForceZeroStoreLifetimes = false;

  std::unique_ptr<OP> m_pOP;

  // LLVM used.
  std::vector<llvm::GlobalVariable *> m_LLVMUsed;

  // Type annotations.
  std::unique_ptr<DxilTypeSystem> m_pTypeSystem;

  // EntryProps for shader functions.
  DxilEntryPropsMap m_DxilEntryPropsMap;

  // Keeps track of patch constant functions used by hull shaders
  std::unordered_set<const llvm::Function *> m_PatchConstantFunctions;

  // Serialized ViewId state.
  std::vector<unsigned> m_SerializedState;

  // properties from HLModule preserved as ShaderFlags
  bool m_bDisableOptimizations = false;
  bool m_bUseMinPrecision = true; // use min precision by default;
  bool m_bAllResourcesBound = false;
  bool m_bResMayAlias = false;

  // properties from HLModule that should not make it to the final DXIL
  uint32_t m_IntermediateFlags = 0;
  uint32_t m_AutoBindingSpace = UINT_MAX;

  std::unique_ptr<DxilSubobjects> m_pSubobjects;

  // m_bMetadataErrors is true if non-fatal metadata errors were encountered.
  // Validator will fail in this case, but should not block module load.
  bool m_bMetadataErrors = false;

  // DXIL metadata serialization/deserialization.
  llvm::MDTuple *EmitDxilResources();
  void LoadDxilResources(const llvm::MDOperand &MDO);

  // Helpers.
  template <typename T>
  unsigned AddResource(std::vector<std::unique_ptr<T>> &Vec,
                       std::unique_ptr<T> pRes);
  void LoadDxilSignature(const llvm::MDTuple *pSigTuple, DxilSignature &Sig,
                         bool bInput);

public:
  // ShaderCompatInfo tracks requirements per-function, subsequently merged into
  // final entry function requirements.
  struct ShaderCompatInfo {
    unsigned minMajor = 6, minMinor = 0;
    // 'mask' is a set of bits representing each compatible shader kind.
    // Mapping is: 1 << (unsigned)DXIL::ShaderKind::<kind>.
    // Starts out with all kinds valid, will be masked down based on features
    // used and by known shader kinds for a particular validation version.
    unsigned mask = ((unsigned)1 << (unsigned)DXIL::ShaderKind::Invalid) - 1;
    ShaderFlags shaderFlags;
    bool Merge(ShaderCompatInfo &other);
  };

  // Compute ShaderCompatInfo for all functions in module.
  void ComputeShaderCompatInfo();

  const ShaderCompatInfo *
  GetCompatInfoForFunction(const llvm::Function *F) const;

private:
  typedef std::unordered_map<const llvm::Function *, ShaderCompatInfo>
      FunctionShaderCompatMap;
  FunctionShaderCompatMap m_FuncToShaderCompat;
  void UpdateFunctionToShaderCompat(const llvm::Function *dxilFunc);
};

} // namespace hlsl
