// Copyright 2015 The Shaderc Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef LIBSHADERC_UTIL_INC_COMPILER_H
#define LIBSHADERC_UTIL_INC_COMPILER_H

#include <array>
#include <cassert>
#include <functional>
#include <mutex>
#include <ostream>
#include <string>
#include <unordered_map>
#include <utility>

#include "glslang/Public/ShaderLang.h"

#include "counting_includer.h"
#include "file_finder.h"
#include "mutex.h"
#include "resources.h"
#include "string_piece.h"

// Fix a typo in glslang/Public/ShaderLang.h
#define EShTargetClientVersion EshTargetClientVersion

namespace shaderc_util {

// To break recursive including. This header is already included in
// spirv_tools_wrapper.h, so cannot include spirv_tools_wrapper.h here.
enum class PassId;

// Initializes glslang on creation, and destroys it on completion.
// Used to tie gslang process operations to object lifetimes.
// Additionally initialization/finalization of glslang is not thread safe, so
// synchronizes these operations.
class GlslangInitializer {
 public:
  GlslangInitializer();
  ~GlslangInitializer();

 private:
  static unsigned int initialize_count_;

  // Using a bare pointer here to avoid any global class construction at the
  // beginning of the execution.
  static std::mutex* glslang_mutex_;
};

// Maps macro names to their definitions.  Stores string_pieces, so the
// underlying strings must outlive it.
using MacroDictionary = std::unordered_map<std::string, std::string>;

// Holds all of the state required to compile source GLSL into SPIR-V.
class Compiler {
 public:
  // Source language
  enum class SourceLanguage {
    GLSL,  // The default
    HLSL,
  };

  // Target environment.
  enum class TargetEnv {
    Vulkan,        // Default to Vulkan 1.0
    OpenGL,        // Default to OpenGL 4.5
    OpenGLCompat,  // Support removed. Generates error if used.
  };

  // Target environment versions.  These numbers match those used by Glslang.
  enum class TargetEnvVersion : uint32_t {
    Default = 0,  // Default for the corresponding target environment
    // For Vulkan, use numbering scheme from vulkan.h
    Vulkan_1_0 = ((1 << 22)),              // Vulkan 1.0
    Vulkan_1_1 = ((1 << 22) | (1 << 12)),  // Vulkan 1.1
    Vulkan_1_2 = ((1 << 22) | (2 << 12)),  // Vulkan 1.2
    Vulkan_1_3 = ((1 << 22) | (3 << 12)),  // Vulkan 1.2
    // For OpenGL, use the numbering from #version in shaders.
    OpenGL_4_5 = 450,
  };

  // SPIR-V version.
  enum class SpirvVersion : uint32_t {
    v1_0 = 0x010000u,
    v1_1 = 0x010100u,
    v1_2 = 0x010200u,
    v1_3 = 0x010300u,
    v1_4 = 0x010400u,
    v1_5 = 0x010500u,
    v1_6 = 0x010600u,
  };

  enum class OutputType {
    SpirvBinary,  // A binary module, as defined by the SPIR-V specification.
    SpirvAssemblyText,  // Assembly syntax defined by the SPIRV-Tools project.
    PreprocessedText,   // Preprocessed source code.
  };

  // Supported optimization levels.
  enum class OptimizationLevel {
    Zero,         // No optimization.
    Size,         // Optimization towards reducing code size.
    Performance,  // Optimization towards better performance.
  };

  // Resource limits.  These map to the "max*" fields in
  // glslang::TBuiltInResource.
  enum class Limit {
#define RESOURCE(NAME, FIELD, CNAME) NAME,
#include "resources.inc"
#undef RESOURCE
  };

  // Types of uniform variables.
  enum class UniformKind {
    // Image, and image buffer.
    Image = 0,
    // Pure sampler.
    Sampler = 1,
    // Sampled texture in GLSL.
    // Shader Resource View, for HLSL.  (Read-only image or storage buffer.)
    Texture = 2,
    // Uniform Buffer Object, or UBO, in GLSL.
    // Also a Cbuffer in HLSL.
    Buffer = 3,
    // Shader Storage Buffer Object, or SSBO
    StorageBuffer = 4,
    // Uniform Access View, in HLSL.  (Writable storage image or storage
    // buffer.)
    UnorderedAccessView = 5,
  };
  enum { kNumUniformKinds = int(UniformKind::UnorderedAccessView) + 1 };

  // Shader pipeline stage.
  // TODO(dneto): Replaces interface uses of EShLanguage with this enum.
  enum class Stage {
    Vertex,
    TessEval,
    TessControl,
    Geometry,
    Fragment,
    Compute,
    RayGenNV,
    IntersectNV,
    AnyHitNV,
    ClosestHitNV,
    MissNV,
    CallableNV,
    TaskNV,
    MeshNV,
    StageEnd,
  };
  enum { kNumStages = int(Stage::StageEnd) };

  // Returns a std::array of all the Stage values.
  const std::array<Stage, kNumStages>& stages() const {
    static std::array<Stage, kNumStages> values{{
        Stage::Vertex,
        Stage::TessEval,
        Stage::TessControl,
        Stage::Geometry,
        Stage::Fragment,
        Stage::Compute,
        Stage::RayGenNV,
        Stage::IntersectNV,
        Stage::AnyHitNV,
        Stage::ClosestHitNV,
        Stage::MissNV,
        Stage::CallableNV,
        Stage::TaskNV,
        Stage::MeshNV,
    }};
    return values;
  }

  // Creates an default compiler instance targeting at Vulkan environment. Uses
  // version 110 and no profile specification as the default for GLSL.
  Compiler()
      // The default version for glsl is 110, or 100 if you are using an es
      // profile. But we want to default to a non-es profile.
      : default_version_(110),
        default_profile_(ENoProfile),
        force_version_profile_(false),
        warnings_as_errors_(false),
        suppress_warnings_(false),
        generate_debug_info_(false),
        enabled_opt_passes_(),
        target_env_(TargetEnv::Vulkan),
        target_env_version_(TargetEnvVersion::Default),
        target_spirv_version_(SpirvVersion::v1_0),
        target_spirv_version_is_forced_(false),
        source_language_(SourceLanguage::GLSL),
        limits_(kDefaultTBuiltInResource),
        auto_bind_uniforms_(false),
        auto_combined_image_sampler_(false),
        auto_binding_base_(),
        auto_map_locations_(false),
        hlsl_iomap_(false),
        hlsl_offsets_(false),
        hlsl_legalization_enabled_(true),
        hlsl_functionality1_enabled_(false),
        hlsl_16bit_types_enabled_(false),
        invert_y_enabled_(false),
        nan_clamp_(false),
        hlsl_explicit_bindings_() {}

  // Requests that the compiler place debug information into the object code,
  // such as identifier names and line numbers.
  void SetGenerateDebugInfo();

  // Sets the optimization level to the given level. Only the last one takes
  // effect if multiple calls of this method exist.
  void SetOptimizationLevel(OptimizationLevel level);

  // Enables or disables HLSL legalization passes.
  void EnableHlslLegalization(bool hlsl_legalization_enabled);

  // Enables or disables extension SPV_GOOGLE_hlsl_functionality1
  void EnableHlslFunctionality1(bool enable);

  // Enables or disables HLSL 16-bit types.
  void EnableHlsl16BitTypes(bool enable);

  // Enables or disables invert position.Y output in vertex shader.
  void EnableInvertY(bool enable);

  // Sets whether the compiler generates code for max and min builtins which,
  // if given a NaN operand, will return the other operand.  Also, the clamp
  // builtin will favour the non-NaN operands, as if clamp were implemented
  // as a composition of max and min.
  void SetNanClamp(bool enable);

  // When a warning is encountered it treat it as an error.
  void SetWarningsAsErrors();

  // Any warning message generated is suppressed before it is output.
  void SetSuppressWarnings();

  // Adds an implicit macro definition obeyed by subsequent CompileShader()
  // calls. The macro and definition should be passed in with their char*
  // pointer and their lengths. They can be modified or deleted after this
  // function has returned.
  void AddMacroDefinition(const char* macro, size_t macro_length,
                          const char* definition, size_t definition_length);

  // Sets the target environment, including version.  The version value should
  // be 0 or one of the values from TargetEnvVersion.  The default value maps
  // to Vulkan 1.0 if the target environment is Vulkan, and it maps to OpenGL
  // 4.5 if the target environment is OpenGL.
  void SetTargetEnv(TargetEnv env,
                    TargetEnvVersion version = TargetEnvVersion::Default);

  // Sets the target version of SPIR-V.  The module will use this version
  // of SPIR-V.  Defaults to the highest version of SPIR-V required to be
  // supported by the target environment.  E.g. default to SPIR-V 1.0 for
  // Vulkan 1.0, and SPIR-V 1.3 for Vulkan 1.1.
  void SetTargetSpirv(SpirvVersion version);

  // Sets the souce language.
  void SetSourceLanguage(SourceLanguage lang);

  // Forces (without any verification) the default version and profile for
  // subsequent CompileShader() calls.
  void SetForcedVersionProfile(int version, EProfile profile);

  // Sets a resource limit.
  void SetLimit(Limit limit, int value);

  // Returns the current limit.
  int GetLimit(Limit limit) const;

  // Set whether the compiler automatically assigns bindings to
  // uniform variables that don't have explicit bindings.
  void SetAutoBindUniforms(bool auto_bind) { auto_bind_uniforms_ = auto_bind; }

  // Sets whether the compiler should automatically remove sampler variables
  // and convert image variables to combined image-sampler variables.
  void SetAutoCombinedImageSampler(bool auto_combine) {
    auto_combined_image_sampler_ = auto_combine;
  }

  // Sets the lowest binding number used when automatically assigning bindings
  // for uniform resources of the given type, for all shader stages.  The default
  // base is zero.
  void SetAutoBindingBase(UniformKind kind, uint32_t base) {
    for (auto stage : stages()) {
      SetAutoBindingBaseForStage(stage, kind, base);
    }
  }

  // Sets the lowest binding number used when automatically assigning bindings
  // for uniform resources of the given type for a specific shader stage.  The
  // default base is zero.
  void SetAutoBindingBaseForStage(Stage stage, UniformKind kind,
                                  uint32_t base) {
    auto_binding_base_[static_cast<int>(stage)][static_cast<int>(kind)] = base;
  }

  // Sets whether the compiler automatically assigns locations to
  // uniform variables that don't have explicit locations.
  void SetAutoMapLocations(bool auto_map) { auto_map_locations_ = auto_map; }

  // Use HLSL IO mapping rules for bindings.  Default is false.
  void SetHlslIoMapping(bool hlsl_iomap) { hlsl_iomap_ = hlsl_iomap; }

  // Use HLSL rules for offsets in "transparent" memory.  These allow for
  // tighter packing of some combinations of types than standard GLSL packings.
  void SetHlslOffsets(bool hlsl_offsets) { hlsl_offsets_ = hlsl_offsets; }

  // Sets an explicit set and binding for the given HLSL register.
  void SetHlslRegisterSetAndBinding(const std::string& reg,
                                    const std::string& set,
                                    const std::string& binding) {
    for (auto stage : stages()) {
      SetHlslRegisterSetAndBindingForStage(stage, reg, set, binding);
    }
  }

  // Sets an explicit set and binding for the given HLSL register in the given
  // shader stage.  For example,
  //    SetHlslRegisterSetAndBinding(Stage::Fragment, "t1", "4", "5")
  // means register "t1" in a fragment shader should map to binding 5 in set 4.
  // (Glslang wants this data as strings, not ints or enums.)  The string data is
  // copied.
  void SetHlslRegisterSetAndBindingForStage(Stage stage, const std::string& reg,
                                            const std::string& set,
                                            const std::string& binding) {
    hlsl_explicit_bindings_[static_cast<int>(stage)].push_back(reg);
    hlsl_explicit_bindings_[static_cast<int>(stage)].push_back(set);
    hlsl_explicit_bindings_[static_cast<int>(stage)].push_back(binding);
  }

  // Compiles the shader source in the input_source_string parameter.
  //
  // If the forced_shader stage parameter is not EShLangCount then
  // the shader is assumed to be of the given stage.
  //
  // For HLSL compilation, entry_point_name is the null-terminated string for
  // the entry point.  For GLSL compilation, entry_point_name is ignored, and
  // compilation assumes the entry point is named "main".
  //
  // The stage_callback function will be called if a shader_stage has
  // not been forced and the stage can not be determined
  // from the shader text. Any #include directives are parsed with the given
  // includer.
  //
  // The initializer parameter must be a valid GlslangInitializer object.
  // Acquire will be called on the initializer and the result will be
  // destroyed before the function ends.
  //
  // The output_type parameter determines what kind of output should be
  // produced.
  //
  // Any error messages are written as if the file name were error_tag.
  // Any errors are written to the error_stream parameter.
  // total_warnings and total_errors are incremented once for every
  // warning or error encountered respectively.
  //
  // Returns a tuple consisting of three fields. 1) a boolean which is true when
  // the compilation succeeded, and false otherwise; 2) a vector of 32-bit words
  // which contains the compilation output data, either compiled SPIR-V binary
  // code, or the text string generated in preprocessing-only or disassembly
  // mode; 3) the size of the output data in bytes. When the output is SPIR-V
  // binary code, the size is the number of bytes of valid data in the vector.
  // If the output is a text string, the size equals the length of that string.
  std::tuple<bool, std::vector<uint32_t>, size_t> Compile(
      const string_piece& input_source_string, EShLanguage forced_shader_stage,
      const std::string& error_tag, const char* entry_point_name,
      const std::function<EShLanguage(std::ostream* error_stream,
                                      const string_piece& error_tag)>&
          stage_callback,
      CountingIncluder& includer, OutputType output_type,
      std::ostream* error_stream, size_t* total_warnings, size_t* total_errors) const;

  static EShMessages GetDefaultRules() {
    return static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules |
                                    EShMsgCascadingErrors);
  }

 protected:
  // Preprocesses a shader whose filename is filename and content is
  // shader_source. If preprocessing is successful, returns true, the
  // preprocessed shader, and any warning message as a tuple. Otherwise,
  // returns false, an empty string, and error messages as a tuple.
  //
  // The error_tag parameter is the name to use for outputting errors.
  // The shader_source parameter is the input shader's source text.
  // The shader_preamble parameter is a context-specific preamble internally
  // prepended to shader_text without affecting the validity of its #version
  // position.
  //
  // Any #include directives are processed with the given includer.
  //
  // If force_version_profile_ is set, the shader's version/profile is forced
  // to be default_version_/default_profile_ regardless of the #version
  // directive in the source code.
  std::tuple<bool, std::string, std::string> PreprocessShader(
      const std::string& error_tag, const string_piece& shader_source,
      const string_piece& shader_preamble, CountingIncluder& includer) const;

  // Cleans up the preamble in a given preprocessed shader.
  //
  // The error_tag parameter is the name to be given for the main file.
  // The pound_extension parameter is the #extension directive we prepended to
  // the original shader source code via preamble.
  // The num_include_directives parameter is the number of #include directives
  // appearing in the original shader source code.
  // The is_for_next_line means whether the #line sets the line number for the
  // next line.
  //
  // If no #include directive is used in the shader source code, we can safely
  // delete the #extension directive we injected via preamble. Otherwise, we
  // need to adjust it if there exists a #version directive in the original
  // shader source code.
  std::string CleanupPreamble(const string_piece& preprocessed_shader,
                              const string_piece& error_tag,
                              const string_piece& pound_extension,
                              int num_include_directives,
                              bool is_for_next_line) const;

  // Determines version and profile from command line, or the source code.
  // Returns the decoded version and profile pair on success. Otherwise,
  // returns (0, ENoProfile).
  std::pair<int, EProfile> DeduceVersionProfile(
      const std::string& preprocessed_shader) const;

  // Determines the shader stage from pragmas embedded in the source text if
  // possible. In the returned pair, the glslang EShLanguage is the shader
  // stage deduced. If no #pragma directives for shader stage exist, it's
  // EShLangCount.  If errors occur, the second element in the pair is the
  // error message.  Otherwise, it's an empty string.
  std::pair<EShLanguage, std::string> GetShaderStageFromSourceCode(
      string_piece filename, const std::string& preprocessed_shader) const;

  // Determines version and profile from command line, or the source code.
  // Returns the decoded version and profile pair on success. Otherwise,
  // returns (0, ENoProfile).
  std::pair<int, EProfile> DeduceVersionProfile(
      const std::string& preprocessed_shader);

  // Gets version and profile specification from the given preprocessedshader.
  // Returns the decoded version and profile pair on success. Otherwise,
  // returns (0, ENoProfile).
  std::pair<int, EProfile> GetVersionProfileFromSourceCode(
      const std::string& preprocessed_shader) const;

  // Version to use when force_version_profile_ is true.
  int default_version_;
  // Profile to use when force_version_profile_ is true.
  EProfile default_profile_;
  // When true, use the default version and profile from eponymous data members.
  bool force_version_profile_;

  // Macro definitions that must be available to reference in the shader source.
  MacroDictionary predefined_macros_;

  // When true, treat warnings as errors.
  bool warnings_as_errors_;
  // Supress warnings when true.
  bool suppress_warnings_;

  // When true, compilation will generate debug info with the binary SPIR-V
  // output.
  bool generate_debug_info_;

  // Optimization passes to be applied.
  std::vector<PassId> enabled_opt_passes_;

  // The target environment to compile with. This controls the glslang
  // EshMessages bitmask, which determines which dialect of GLSL and which
  // SPIR-V codegen semantics are used. This impacts the warning & error
  // messages as well as the set of available builtins, as per the
  // implementation of glslang.
  TargetEnv target_env_;

  // The version number of the target environment.  The numbering scheme is
  // particular to each target environment.  If this is 0, then use a default
  // for that particular target environment. See libshaders/shaderc/shaderc.h
  // for those defaults.
  TargetEnvVersion target_env_version_;

  // The SPIR-V version to be used for the generated module.  Defaults to 1.0.
  SpirvVersion target_spirv_version_;
  // True if the user explicitly set the target SPIR-V version.
  bool target_spirv_version_is_forced_;

  // The source language.  Defaults to GLSL.
  SourceLanguage source_language_;

  // The resource limits to be used.
  TBuiltInResource limits_;

  // True if the compiler should automatically bind uniforms that don't
  // have explicit bindings.
  bool auto_bind_uniforms_;

  // True if the compiler should automatically remove sampler variables
  // and convert image variables to combined image-sampler variables.
  bool auto_combined_image_sampler_;

  // The base binding number per uniform type, per stage, used when automatically
  // binding uniforms that don't hzve explicit bindings in the shader source.
  // The default is zero.
  uint32_t auto_binding_base_[kNumStages][kNumUniformKinds];

  // True if the compiler should automatically map uniforms that don't
  // have explicit locations.
  bool auto_map_locations_;

  // True if the compiler should use HLSL IO mapping rules when compiling HLSL.
  bool hlsl_iomap_;

  // True if the compiler should determine block member offsets using HLSL
  // packing rules instead of standard GLSL rules.
  bool hlsl_offsets_;

  // True if the compiler should perform legalization optimization passes if
  // source language is HLSL.
  bool hlsl_legalization_enabled_;

  // True if the compiler should support extension SPV_GOOGLE_hlsl_functionality1.
  bool hlsl_functionality1_enabled_;

  // True if the compiler should support 16-bit HLSL types.
  bool hlsl_16bit_types_enabled_;

  // True if the compiler should invert position.Y output in vertex shader.
  bool invert_y_enabled_;

  // True if the compiler generates code for max and min builtins which,
  // if given a NaN operand, will return the other operand.  Also, the clamp
  // builtin will favour the non-NaN operands, as if clamp were implemented
  // as a composition of max and min.
  bool nan_clamp_;

  // A sequence of triples, each triple representing a specific HLSL register
  // name, and the set and binding numbers it should be mapped to, but in
  // the form of strings.  This is how Glslang wants to consume the data.
  std::vector<std::string> hlsl_explicit_bindings_[kNumStages];
};

// Converts a string to a vector of uint32_t by copying the content of a given
// string to the vector and returns it. Appends '\0' at the end if extra bytes
// are required to complete the last element.
std::vector<uint32_t> ConvertStringToVector(const std::string& str);

// Converts a valid Glslang shader stage value to a Compiler::Stage value.
inline Compiler::Stage ConvertToStage(EShLanguage stage) {
  switch (stage) {
    case EShLangVertex:
      return Compiler::Stage::Vertex;
    case EShLangTessControl:
      return Compiler::Stage::TessEval;
    case EShLangTessEvaluation:
      return Compiler::Stage::TessControl;
    case EShLangGeometry:
      return Compiler::Stage::Geometry;
    case EShLangFragment:
      return Compiler::Stage::Fragment;
    case EShLangCompute:
      return Compiler::Stage::Compute;
    case EShLangRayGenNV:
      return Compiler::Stage::RayGenNV;
    case EShLangIntersectNV:
      return Compiler::Stage::IntersectNV;
    case EShLangAnyHitNV:
      return Compiler::Stage::AnyHitNV;
    case EShLangClosestHitNV:
      return Compiler::Stage::ClosestHitNV;
    case EShLangMissNV:
      return Compiler::Stage::MissNV;
    case EShLangCallableNV:
      return Compiler::Stage::CallableNV;
    case EShLangTaskNV:
      return Compiler::Stage::TaskNV;
    case EShLangMeshNV:
      return Compiler::Stage::MeshNV;
    default:
      break;
  }
  assert(false && "Invalid case");
  return Compiler::Stage::Compute;
}

// A GlslangClientInfo captures target client version and desired SPIR-V
// version.
struct GlslangClientInfo {
  GlslangClientInfo() {}
  GlslangClientInfo(const std::string& e, glslang::EShClient c,
                    glslang::EShTargetClientVersion cv,
                    glslang::EShTargetLanguage l,
                    glslang::EShTargetLanguageVersion lv)
      : error(e),
        client(c),
        client_version(cv),
        target_language(l),
        target_language_version(lv) {}

  std::string error;  // Empty if ok, otherwise contains the error message.
  glslang::EShClient client = glslang::EShClientNone;
  glslang::EShTargetClientVersion client_version;
  glslang::EShTargetLanguage target_language = glslang::EShTargetSpv;
  glslang::EShTargetLanguageVersion target_language_version =
      glslang::EShTargetSpv_1_0;
};

// Returns the mappings to Glslang client, client version, and SPIR-V version.
// Also indicates whether the input values were valid.
GlslangClientInfo GetGlslangClientInfo(
    const std::string& error_tag,  // Indicates source location, for errors.
    shaderc_util::Compiler::TargetEnv env,
    shaderc_util::Compiler::TargetEnvVersion env_version,
    shaderc_util::Compiler::SpirvVersion spv_version,
    bool spv_version_is_forced);

}  // namespace shaderc_util
#endif  // LIBSHADERC_UTIL_INC_COMPILER_H
