// Copyright 2016 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.

#include "libshaderc_util/spirv_tools_wrapper.h"

#include <algorithm>
#include <sstream>

#include "spirv-tools/libspirv.hpp"
#include "spirv-tools/optimizer.hpp"

namespace shaderc_util {

namespace {

// Gets the corresponding target environment used in SPIRV-Tools.
spv_target_env GetSpirvToolsTargetEnv(Compiler::TargetEnv env,
                                      Compiler::TargetEnvVersion version) {
  switch (env) {
    case Compiler::TargetEnv::Vulkan:
      switch (version) {
        case Compiler::TargetEnvVersion::Default:
          return SPV_ENV_VULKAN_1_0;
        case Compiler::TargetEnvVersion::Vulkan_1_0:
          return SPV_ENV_VULKAN_1_0;
        case Compiler::TargetEnvVersion::Vulkan_1_1:
          return SPV_ENV_VULKAN_1_1;
        case Compiler::TargetEnvVersion::Vulkan_1_2:
          return SPV_ENV_VULKAN_1_2;
        case Compiler::TargetEnvVersion::Vulkan_1_3:
          return SPV_ENV_VULKAN_1_3;
        default:
          break;
      }
      break;
    case Compiler::TargetEnv::OpenGL:
      return SPV_ENV_OPENGL_4_5;
    case Compiler::TargetEnv::OpenGLCompat:
      // Errors out before getting here.  But the compiler wants us to handle
      // enum anyway.
      return SPV_ENV_OPENGL_4_5;
  }
  assert(false && "unexpected target environment or version");
  return SPV_ENV_VULKAN_1_0;
}

}  // anonymous namespace

bool SpirvToolsDisassemble(Compiler::TargetEnv env,
                           Compiler::TargetEnvVersion version,
                           const std::vector<uint32_t>& binary,
                           std::string* text_or_error) {
  spvtools::SpirvTools tools(GetSpirvToolsTargetEnv(env, version));
  std::ostringstream oss;
  tools.SetMessageConsumer([&oss](spv_message_level_t, const char*,
                                  const spv_position_t& position,
                                  const char* message) {
    oss << position.index << ": " << message;
  });
  const bool success =
      tools.Disassemble(binary, text_or_error,
                        SPV_BINARY_TO_TEXT_OPTION_INDENT |
                            SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
  if (!success) {
    *text_or_error = oss.str();
  }
  return success;
}

bool SpirvToolsAssemble(Compiler::TargetEnv env,
                        Compiler::TargetEnvVersion version,
                        const string_piece assembly, spv_binary* binary,
                        std::string* errors) {
  auto spvtools_context =
      spvContextCreate(GetSpirvToolsTargetEnv(env, version));
  spv_diagnostic spvtools_diagnostic = nullptr;

  *binary = nullptr;
  errors->clear();

  const bool success =
      spvTextToBinary(spvtools_context, assembly.data(), assembly.size(),
                      binary, &spvtools_diagnostic) == SPV_SUCCESS;
  if (!success) {
    std::ostringstream oss;
    oss << spvtools_diagnostic->position.line + 1 << ":"
        << spvtools_diagnostic->position.column + 1 << ": "
        << spvtools_diagnostic->error;
    *errors = oss.str();
  }

  spvDiagnosticDestroy(spvtools_diagnostic);
  spvContextDestroy(spvtools_context);

  return success;
}

bool SpirvToolsOptimize(Compiler::TargetEnv env,
                        Compiler::TargetEnvVersion version,
                        const std::vector<PassId>& enabled_passes,
                        spvtools::OptimizerOptions& optimizer_options,
                        std::vector<uint32_t>* binary, std::string* errors) {
  errors->clear();
  if (enabled_passes.empty()) return true;
  if (std::all_of(
          enabled_passes.cbegin(), enabled_passes.cend(),
          [](const PassId& pass) { return pass == PassId::kNullPass; })) {
    return true;
  }

  spvtools::ValidatorOptions val_opts;
  // This allows flexible memory layout for HLSL.
  val_opts.SetSkipBlockLayout(true);
  // This allows HLSL legalization regarding resources.
  val_opts.SetRelaxLogicalPointer(true);
  // This uses relaxed rules for pre-legalized HLSL.
  val_opts.SetBeforeHlslLegalization(true);

  // Set additional optimizer options.
  optimizer_options.set_validator_options(val_opts);
  optimizer_options.set_run_validator(true);

  spvtools::Optimizer optimizer(GetSpirvToolsTargetEnv(env, version));

  std::ostringstream oss;
  optimizer.SetMessageConsumer(
      [&oss](spv_message_level_t, const char*, const spv_position_t&,
             const char* message) { oss << message << "\n"; });

  for (const auto& pass : enabled_passes) {
    switch (pass) {
      case PassId::kLegalizationPasses:
        optimizer.RegisterLegalizationPasses();
        break;
      case PassId::kPerformancePasses:
        optimizer.RegisterPerformancePasses();
        break;
      case PassId::kSizePasses:
        optimizer.RegisterSizePasses();
        break;
      case PassId::kNullPass:
        // We actually don't need to do anything for null pass.
        break;
      case PassId::kStripDebugInfo:
        optimizer.RegisterPass(spvtools::CreateStripDebugInfoPass());
        break;
      case PassId::kCompactIds:
        optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
        break;
    }
  }

  if (!optimizer.Run(binary->data(), binary->size(), binary,
                     optimizer_options)) {
    *errors = oss.str();
    return false;
  }
  return true;
}

}  // namespace shaderc_util
