// Copyright (c) 2017 Google Inc.
//
// 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 "source/opt/ir_context.h"

#include <cstring>

#include "OpenCLDebugInfo100.h"
#include "source/latest_version_glsl_std_450_header.h"
#include "source/opt/log.h"
#include "source/opt/mem_pass.h"
#include "source/opt/reflect.h"

namespace {

static const int kSpvDecorateTargetIdInIdx = 0;
static const int kSpvDecorateDecorationInIdx = 1;
static const int kSpvDecorateBuiltinInIdx = 2;
static const int kEntryPointInterfaceInIdx = 3;
static const int kEntryPointFunctionIdInIdx = 1;

// Constants for OpenCL.DebugInfo.100 extension instructions.
static const uint32_t kDebugFunctionOperandFunctionIndex = 13;
static const uint32_t kDebugGlobalVariableOperandVariableIndex = 11;

}  // anonymous namespace

namespace spvtools {
namespace opt {

void IRContext::BuildInvalidAnalyses(IRContext::Analysis set) {
  if (set & kAnalysisDefUse) {
    BuildDefUseManager();
  }
  if (set & kAnalysisInstrToBlockMapping) {
    BuildInstrToBlockMapping();
  }
  if (set & kAnalysisDecorations) {
    BuildDecorationManager();
  }
  if (set & kAnalysisCFG) {
    BuildCFG();
  }
  if (set & kAnalysisDominatorAnalysis) {
    ResetDominatorAnalysis();
  }
  if (set & kAnalysisLoopAnalysis) {
    ResetLoopAnalysis();
  }
  if (set & kAnalysisBuiltinVarId) {
    ResetBuiltinAnalysis();
  }
  if (set & kAnalysisNameMap) {
    BuildIdToNameMap();
  }
  if (set & kAnalysisScalarEvolution) {
    BuildScalarEvolutionAnalysis();
  }
  if (set & kAnalysisRegisterPressure) {
    BuildRegPressureAnalysis();
  }
  if (set & kAnalysisValueNumberTable) {
    BuildValueNumberTable();
  }
  if (set & kAnalysisStructuredCFG) {
    BuildStructuredCFGAnalysis();
  }
  if (set & kAnalysisIdToFuncMapping) {
    BuildIdToFuncMapping();
  }
  if (set & kAnalysisConstants) {
    BuildConstantManager();
  }
  if (set & kAnalysisTypes) {
    BuildTypeManager();
  }
  if (set & kAnalysisDebugInfo) {
    BuildDebugInfoManager();
  }
}

void IRContext::InvalidateAnalysesExceptFor(
    IRContext::Analysis preserved_analyses) {
  uint32_t analyses_to_invalidate = valid_analyses_ & (~preserved_analyses);
  InvalidateAnalyses(static_cast<IRContext::Analysis>(analyses_to_invalidate));
}

void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) {
  // The ConstantManager and DebugInfoManager contain Type pointers. If the
  // TypeManager goes away, the ConstantManager and DebugInfoManager have to
  // go away.
  if (analyses_to_invalidate & kAnalysisTypes) {
    analyses_to_invalidate |= kAnalysisConstants;
    analyses_to_invalidate |= kAnalysisDebugInfo;
  }

  // The dominator analysis hold the psuedo entry and exit nodes from the CFG.
  // Also if the CFG change the dominators many changed as well, so the
  // dominator analysis should be invalidated as well.
  if (analyses_to_invalidate & kAnalysisCFG) {
    analyses_to_invalidate |= kAnalysisDominatorAnalysis;
  }

  if (analyses_to_invalidate & kAnalysisDefUse) {
    def_use_mgr_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisInstrToBlockMapping) {
    instr_to_block_.clear();
  }
  if (analyses_to_invalidate & kAnalysisDecorations) {
    decoration_mgr_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisCombinators) {
    combinator_ops_.clear();
  }
  if (analyses_to_invalidate & kAnalysisBuiltinVarId) {
    builtin_var_id_map_.clear();
  }
  if (analyses_to_invalidate & kAnalysisCFG) {
    cfg_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisDominatorAnalysis) {
    dominator_trees_.clear();
    post_dominator_trees_.clear();
  }
  if (analyses_to_invalidate & kAnalysisNameMap) {
    id_to_name_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisValueNumberTable) {
    vn_table_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisStructuredCFG) {
    struct_cfg_analysis_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisIdToFuncMapping) {
    id_to_func_.clear();
  }
  if (analyses_to_invalidate & kAnalysisConstants) {
    constant_mgr_.reset(nullptr);
  }
  if (analyses_to_invalidate & kAnalysisTypes) {
    type_mgr_.reset(nullptr);
  }

  if (analyses_to_invalidate & kAnalysisDebugInfo) {
    debug_info_mgr_.reset(nullptr);
  }

  valid_analyses_ = Analysis(valid_analyses_ & ~analyses_to_invalidate);
}

Instruction* IRContext::KillInst(Instruction* inst) {
  if (!inst) {
    return nullptr;
  }

  KillNamesAndDecorates(inst);

  KillOperandFromDebugInstructions(inst);

  if (AreAnalysesValid(kAnalysisDefUse)) {
    get_def_use_mgr()->ClearInst(inst);
  }
  if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) {
    instr_to_block_.erase(inst);
  }
  if (AreAnalysesValid(kAnalysisDecorations)) {
    if (inst->IsDecoration()) {
      decoration_mgr_->RemoveDecoration(inst);
    }
  }
  if (AreAnalysesValid(kAnalysisDebugInfo)) {
    get_debug_info_mgr()->ClearDebugScopeAndInlinedAtUses(inst);
    get_debug_info_mgr()->ClearDebugInfo(inst);
  }
  if (type_mgr_ && IsTypeInst(inst->opcode())) {
    type_mgr_->RemoveId(inst->result_id());
  }
  if (constant_mgr_ && IsConstantInst(inst->opcode())) {
    constant_mgr_->RemoveId(inst->result_id());
  }
  if (inst->opcode() == SpvOpCapability || inst->opcode() == SpvOpExtension) {
    // We reset the feature manager, instead of updating it, because it is just
    // as much work.  We would have to remove all capabilities implied by this
    // capability that are not also implied by the remaining OpCapability
    // instructions. We could update extensions, but we will see if it is
    // needed.
    ResetFeatureManager();
  }

  RemoveFromIdToName(inst);

  Instruction* next_instruction = nullptr;
  if (inst->IsInAList()) {
    next_instruction = inst->NextNode();
    inst->RemoveFromList();
    delete inst;
  } else {
    // Needed for instructions that are not part of a list like OpLabels,
    // OpFunction, OpFunctionEnd, etc..
    inst->ToNop();
  }
  return next_instruction;
}

void IRContext::KillNonSemanticInfo(Instruction* inst) {
  if (!inst->HasResultId()) return;
  std::vector<Instruction*> work_list;
  std::vector<Instruction*> to_kill;
  std::unordered_set<Instruction*> seen;
  work_list.push_back(inst);

  while (!work_list.empty()) {
    auto* i = work_list.back();
    work_list.pop_back();
    get_def_use_mgr()->ForEachUser(
        i, [&work_list, &to_kill, &seen](Instruction* user) {
          if (user->IsNonSemanticInstruction() && seen.insert(user).second) {
            work_list.push_back(user);
            to_kill.push_back(user);
          }
        });
  }

  for (auto* dead : to_kill) {
    KillInst(dead);
  }
}

bool IRContext::KillDef(uint32_t id) {
  Instruction* def = get_def_use_mgr()->GetDef(id);
  if (def != nullptr) {
    KillInst(def);
    return true;
  }
  return false;
}

bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
  return ReplaceAllUsesWithPredicate(before, after,
                                     [](Instruction*) { return true; });
}

bool IRContext::ReplaceAllUsesWithPredicate(
    uint32_t before, uint32_t after,
    const std::function<bool(Instruction*)>& predicate) {
  if (before == after) return false;

  if (AreAnalysesValid(kAnalysisDebugInfo)) {
    get_debug_info_mgr()->ReplaceAllUsesInDebugScopeWithPredicate(before, after,
                                                                  predicate);
  }

  // Ensure that |after| has been registered as def.
  assert(get_def_use_mgr()->GetDef(after) &&
         "'after' is not a registered def.");

  std::vector<std::pair<Instruction*, uint32_t>> uses_to_update;
  get_def_use_mgr()->ForEachUse(
      before, [&predicate, &uses_to_update](Instruction* user, uint32_t index) {
        if (predicate(user)) {
          uses_to_update.emplace_back(user, index);
        }
      });

  Instruction* prev = nullptr;
  for (auto p : uses_to_update) {
    Instruction* user = p.first;
    uint32_t index = p.second;
    if (prev == nullptr || prev != user) {
      ForgetUses(user);
      prev = user;
    }
    const uint32_t type_result_id_count =
        (user->result_id() != 0) + (user->type_id() != 0);

    if (index < type_result_id_count) {
      // Update the type_id. Note that result id is immutable so it should
      // never be updated.
      if (user->type_id() != 0 && index == 0) {
        user->SetResultType(after);
      } else if (user->type_id() == 0) {
        SPIRV_ASSERT(consumer_, false,
                     "Result type id considered as use while the instruction "
                     "doesn't have a result type id.");
        (void)consumer_;  // Makes the compiler happy for release build.
      } else {
        SPIRV_ASSERT(consumer_, false,
                     "Trying setting the immutable result id.");
      }
    } else {
      // Update an in-operand.
      uint32_t in_operand_pos = index - type_result_id_count;
      // Make the modification in the instruction.
      user->SetInOperand(in_operand_pos, {after});
    }
    AnalyzeUses(user);
  }
  return true;
}

bool IRContext::IsConsistent() {
#ifndef SPIRV_CHECK_CONTEXT
  return true;
#else
  if (AreAnalysesValid(kAnalysisDefUse)) {
    analysis::DefUseManager new_def_use(module());
    if (*get_def_use_mgr() != new_def_use) {
      return false;
    }
  }

  if (AreAnalysesValid(kAnalysisIdToFuncMapping)) {
    for (auto& fn : *module_) {
      if (id_to_func_[fn.result_id()] != &fn) {
        return false;
      }
    }
  }

  if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) {
    for (auto& func : *module()) {
      for (auto& block : func) {
        if (!block.WhileEachInst([this, &block](Instruction* inst) {
              if (get_instr_block(inst) != &block) {
                return false;
              }
              return true;
            }))
          return false;
      }
    }
  }

  if (!CheckCFG()) {
    return false;
  }

  if (AreAnalysesValid(kAnalysisDecorations)) {
    analysis::DecorationManager* dec_mgr = get_decoration_mgr();
    analysis::DecorationManager current(module());

    if (*dec_mgr != current) {
      return false;
    }
  }

  if (feature_mgr_ != nullptr) {
    FeatureManager current(grammar_);
    current.Analyze(module());

    if (current != *feature_mgr_) {
      return false;
    }
  }
  return true;
#endif
}

void IRContext::ForgetUses(Instruction* inst) {
  if (AreAnalysesValid(kAnalysisDefUse)) {
    get_def_use_mgr()->EraseUseRecordsOfOperandIds(inst);
  }
  if (AreAnalysesValid(kAnalysisDecorations)) {
    if (inst->IsDecoration()) {
      get_decoration_mgr()->RemoveDecoration(inst);
    }
  }
  if (AreAnalysesValid(kAnalysisDebugInfo)) {
    get_debug_info_mgr()->ClearDebugInfo(inst);
  }
  RemoveFromIdToName(inst);
}

void IRContext::AnalyzeUses(Instruction* inst) {
  if (AreAnalysesValid(kAnalysisDefUse)) {
    get_def_use_mgr()->AnalyzeInstUse(inst);
  }
  if (AreAnalysesValid(kAnalysisDecorations)) {
    if (inst->IsDecoration()) {
      get_decoration_mgr()->AddDecoration(inst);
    }
  }
  if (AreAnalysesValid(kAnalysisDebugInfo)) {
    get_debug_info_mgr()->AnalyzeDebugInst(inst);
  }
  if (id_to_name_ &&
      (inst->opcode() == SpvOpName || inst->opcode() == SpvOpMemberName)) {
    id_to_name_->insert({inst->GetSingleWordInOperand(0), inst});
  }
}

void IRContext::KillNamesAndDecorates(uint32_t id) {
  analysis::DecorationManager* dec_mgr = get_decoration_mgr();
  dec_mgr->RemoveDecorationsFrom(id);

  std::vector<Instruction*> name_to_kill;
  for (auto name : GetNames(id)) {
    name_to_kill.push_back(name.second);
  }
  for (Instruction* name_inst : name_to_kill) {
    KillInst(name_inst);
  }
}

void IRContext::KillNamesAndDecorates(Instruction* inst) {
  const uint32_t rId = inst->result_id();
  if (rId == 0) return;
  KillNamesAndDecorates(rId);
}

void IRContext::KillOperandFromDebugInstructions(Instruction* inst) {
  const auto opcode = inst->opcode();
  const uint32_t id = inst->result_id();
  // Kill id of OpFunction from DebugFunction.
  if (opcode == SpvOpFunction) {
    for (auto it = module()->ext_inst_debuginfo_begin();
         it != module()->ext_inst_debuginfo_end(); ++it) {
      if (it->GetOpenCL100DebugOpcode() != OpenCLDebugInfo100DebugFunction)
        continue;
      auto& operand = it->GetOperand(kDebugFunctionOperandFunctionIndex);
      if (operand.words[0] == id) {
        operand.words[0] =
            get_debug_info_mgr()->GetDebugInfoNone()->result_id();
        get_def_use_mgr()->AnalyzeInstUse(&*it);
      }
    }
  }
  // Kill id of OpVariable for global variable from DebugGlobalVariable.
  if (opcode == SpvOpVariable || IsConstantInst(opcode)) {
    for (auto it = module()->ext_inst_debuginfo_begin();
         it != module()->ext_inst_debuginfo_end(); ++it) {
      if (it->GetOpenCL100DebugOpcode() !=
          OpenCLDebugInfo100DebugGlobalVariable)
        continue;
      auto& operand = it->GetOperand(kDebugGlobalVariableOperandVariableIndex);
      if (operand.words[0] == id) {
        operand.words[0] =
            get_debug_info_mgr()->GetDebugInfoNone()->result_id();
        get_def_use_mgr()->AnalyzeInstUse(&*it);
      }
    }
  }
}

void IRContext::AddCombinatorsForCapability(uint32_t capability) {
  if (capability == SpvCapabilityShader) {
    combinator_ops_[0].insert({SpvOpNop,
                               SpvOpUndef,
                               SpvOpConstant,
                               SpvOpConstantTrue,
                               SpvOpConstantFalse,
                               SpvOpConstantComposite,
                               SpvOpConstantSampler,
                               SpvOpConstantNull,
                               SpvOpTypeVoid,
                               SpvOpTypeBool,
                               SpvOpTypeInt,
                               SpvOpTypeFloat,
                               SpvOpTypeVector,
                               SpvOpTypeMatrix,
                               SpvOpTypeImage,
                               SpvOpTypeSampler,
                               SpvOpTypeSampledImage,
                               SpvOpTypeAccelerationStructureNV,
                               SpvOpTypeAccelerationStructureKHR,
                               SpvOpTypeRayQueryKHR,
                               SpvOpTypeArray,
                               SpvOpTypeRuntimeArray,
                               SpvOpTypeStruct,
                               SpvOpTypeOpaque,
                               SpvOpTypePointer,
                               SpvOpTypeFunction,
                               SpvOpTypeEvent,
                               SpvOpTypeDeviceEvent,
                               SpvOpTypeReserveId,
                               SpvOpTypeQueue,
                               SpvOpTypePipe,
                               SpvOpTypeForwardPointer,
                               SpvOpVariable,
                               SpvOpImageTexelPointer,
                               SpvOpLoad,
                               SpvOpAccessChain,
                               SpvOpInBoundsAccessChain,
                               SpvOpArrayLength,
                               SpvOpVectorExtractDynamic,
                               SpvOpVectorInsertDynamic,
                               SpvOpVectorShuffle,
                               SpvOpCompositeConstruct,
                               SpvOpCompositeExtract,
                               SpvOpCompositeInsert,
                               SpvOpCopyObject,
                               SpvOpTranspose,
                               SpvOpSampledImage,
                               SpvOpImageSampleImplicitLod,
                               SpvOpImageSampleExplicitLod,
                               SpvOpImageSampleDrefImplicitLod,
                               SpvOpImageSampleDrefExplicitLod,
                               SpvOpImageSampleProjImplicitLod,
                               SpvOpImageSampleProjExplicitLod,
                               SpvOpImageSampleProjDrefImplicitLod,
                               SpvOpImageSampleProjDrefExplicitLod,
                               SpvOpImageFetch,
                               SpvOpImageGather,
                               SpvOpImageDrefGather,
                               SpvOpImageRead,
                               SpvOpImage,
                               SpvOpImageQueryFormat,
                               SpvOpImageQueryOrder,
                               SpvOpImageQuerySizeLod,
                               SpvOpImageQuerySize,
                               SpvOpImageQueryLevels,
                               SpvOpImageQuerySamples,
                               SpvOpConvertFToU,
                               SpvOpConvertFToS,
                               SpvOpConvertSToF,
                               SpvOpConvertUToF,
                               SpvOpUConvert,
                               SpvOpSConvert,
                               SpvOpFConvert,
                               SpvOpQuantizeToF16,
                               SpvOpBitcast,
                               SpvOpSNegate,
                               SpvOpFNegate,
                               SpvOpIAdd,
                               SpvOpFAdd,
                               SpvOpISub,
                               SpvOpFSub,
                               SpvOpIMul,
                               SpvOpFMul,
                               SpvOpUDiv,
                               SpvOpSDiv,
                               SpvOpFDiv,
                               SpvOpUMod,
                               SpvOpSRem,
                               SpvOpSMod,
                               SpvOpFRem,
                               SpvOpFMod,
                               SpvOpVectorTimesScalar,
                               SpvOpMatrixTimesScalar,
                               SpvOpVectorTimesMatrix,
                               SpvOpMatrixTimesVector,
                               SpvOpMatrixTimesMatrix,
                               SpvOpOuterProduct,
                               SpvOpDot,
                               SpvOpIAddCarry,
                               SpvOpISubBorrow,
                               SpvOpUMulExtended,
                               SpvOpSMulExtended,
                               SpvOpAny,
                               SpvOpAll,
                               SpvOpIsNan,
                               SpvOpIsInf,
                               SpvOpLogicalEqual,
                               SpvOpLogicalNotEqual,
                               SpvOpLogicalOr,
                               SpvOpLogicalAnd,
                               SpvOpLogicalNot,
                               SpvOpSelect,
                               SpvOpIEqual,
                               SpvOpINotEqual,
                               SpvOpUGreaterThan,
                               SpvOpSGreaterThan,
                               SpvOpUGreaterThanEqual,
                               SpvOpSGreaterThanEqual,
                               SpvOpULessThan,
                               SpvOpSLessThan,
                               SpvOpULessThanEqual,
                               SpvOpSLessThanEqual,
                               SpvOpFOrdEqual,
                               SpvOpFUnordEqual,
                               SpvOpFOrdNotEqual,
                               SpvOpFUnordNotEqual,
                               SpvOpFOrdLessThan,
                               SpvOpFUnordLessThan,
                               SpvOpFOrdGreaterThan,
                               SpvOpFUnordGreaterThan,
                               SpvOpFOrdLessThanEqual,
                               SpvOpFUnordLessThanEqual,
                               SpvOpFOrdGreaterThanEqual,
                               SpvOpFUnordGreaterThanEqual,
                               SpvOpShiftRightLogical,
                               SpvOpShiftRightArithmetic,
                               SpvOpShiftLeftLogical,
                               SpvOpBitwiseOr,
                               SpvOpBitwiseXor,
                               SpvOpBitwiseAnd,
                               SpvOpNot,
                               SpvOpBitFieldInsert,
                               SpvOpBitFieldSExtract,
                               SpvOpBitFieldUExtract,
                               SpvOpBitReverse,
                               SpvOpBitCount,
                               SpvOpPhi,
                               SpvOpImageSparseSampleImplicitLod,
                               SpvOpImageSparseSampleExplicitLod,
                               SpvOpImageSparseSampleDrefImplicitLod,
                               SpvOpImageSparseSampleDrefExplicitLod,
                               SpvOpImageSparseSampleProjImplicitLod,
                               SpvOpImageSparseSampleProjExplicitLod,
                               SpvOpImageSparseSampleProjDrefImplicitLod,
                               SpvOpImageSparseSampleProjDrefExplicitLod,
                               SpvOpImageSparseFetch,
                               SpvOpImageSparseGather,
                               SpvOpImageSparseDrefGather,
                               SpvOpImageSparseTexelsResident,
                               SpvOpImageSparseRead,
                               SpvOpSizeOf});
  }
}

void IRContext::AddCombinatorsForExtension(Instruction* extension) {
  assert(extension->opcode() == SpvOpExtInstImport &&
         "Expecting an import of an extension's instruction set.");
  const char* extension_name =
      reinterpret_cast<const char*>(&extension->GetInOperand(0).words[0]);
  if (!strcmp(extension_name, "GLSL.std.450")) {
    combinator_ops_[extension->result_id()] = {GLSLstd450Round,
                                               GLSLstd450RoundEven,
                                               GLSLstd450Trunc,
                                               GLSLstd450FAbs,
                                               GLSLstd450SAbs,
                                               GLSLstd450FSign,
                                               GLSLstd450SSign,
                                               GLSLstd450Floor,
                                               GLSLstd450Ceil,
                                               GLSLstd450Fract,
                                               GLSLstd450Radians,
                                               GLSLstd450Degrees,
                                               GLSLstd450Sin,
                                               GLSLstd450Cos,
                                               GLSLstd450Tan,
                                               GLSLstd450Asin,
                                               GLSLstd450Acos,
                                               GLSLstd450Atan,
                                               GLSLstd450Sinh,
                                               GLSLstd450Cosh,
                                               GLSLstd450Tanh,
                                               GLSLstd450Asinh,
                                               GLSLstd450Acosh,
                                               GLSLstd450Atanh,
                                               GLSLstd450Atan2,
                                               GLSLstd450Pow,
                                               GLSLstd450Exp,
                                               GLSLstd450Log,
                                               GLSLstd450Exp2,
                                               GLSLstd450Log2,
                                               GLSLstd450Sqrt,
                                               GLSLstd450InverseSqrt,
                                               GLSLstd450Determinant,
                                               GLSLstd450MatrixInverse,
                                               GLSLstd450ModfStruct,
                                               GLSLstd450FMin,
                                               GLSLstd450UMin,
                                               GLSLstd450SMin,
                                               GLSLstd450FMax,
                                               GLSLstd450UMax,
                                               GLSLstd450SMax,
                                               GLSLstd450FClamp,
                                               GLSLstd450UClamp,
                                               GLSLstd450SClamp,
                                               GLSLstd450FMix,
                                               GLSLstd450IMix,
                                               GLSLstd450Step,
                                               GLSLstd450SmoothStep,
                                               GLSLstd450Fma,
                                               GLSLstd450FrexpStruct,
                                               GLSLstd450Ldexp,
                                               GLSLstd450PackSnorm4x8,
                                               GLSLstd450PackUnorm4x8,
                                               GLSLstd450PackSnorm2x16,
                                               GLSLstd450PackUnorm2x16,
                                               GLSLstd450PackHalf2x16,
                                               GLSLstd450PackDouble2x32,
                                               GLSLstd450UnpackSnorm2x16,
                                               GLSLstd450UnpackUnorm2x16,
                                               GLSLstd450UnpackHalf2x16,
                                               GLSLstd450UnpackSnorm4x8,
                                               GLSLstd450UnpackUnorm4x8,
                                               GLSLstd450UnpackDouble2x32,
                                               GLSLstd450Length,
                                               GLSLstd450Distance,
                                               GLSLstd450Cross,
                                               GLSLstd450Normalize,
                                               GLSLstd450FaceForward,
                                               GLSLstd450Reflect,
                                               GLSLstd450Refract,
                                               GLSLstd450FindILsb,
                                               GLSLstd450FindSMsb,
                                               GLSLstd450FindUMsb,
                                               GLSLstd450InterpolateAtCentroid,
                                               GLSLstd450InterpolateAtSample,
                                               GLSLstd450InterpolateAtOffset,
                                               GLSLstd450NMin,
                                               GLSLstd450NMax,
                                               GLSLstd450NClamp};
  } else {
    // Map the result id to the empty set.
    combinator_ops_[extension->result_id()];
  }
}

void IRContext::InitializeCombinators() {
  get_feature_mgr()->GetCapabilities()->ForEach(
      [this](SpvCapability cap) { AddCombinatorsForCapability(cap); });

  for (auto& extension : module()->ext_inst_imports()) {
    AddCombinatorsForExtension(&extension);
  }

  valid_analyses_ |= kAnalysisCombinators;
}

void IRContext::RemoveFromIdToName(const Instruction* inst) {
  if (id_to_name_ &&
      (inst->opcode() == SpvOpName || inst->opcode() == SpvOpMemberName)) {
    auto range = id_to_name_->equal_range(inst->GetSingleWordInOperand(0));
    for (auto it = range.first; it != range.second; ++it) {
      if (it->second == inst) {
        id_to_name_->erase(it);
        break;
      }
    }
  }
}

LoopDescriptor* IRContext::GetLoopDescriptor(const Function* f) {
  if (!AreAnalysesValid(kAnalysisLoopAnalysis)) {
    ResetLoopAnalysis();
  }

  std::unordered_map<const Function*, LoopDescriptor>::iterator it =
      loop_descriptors_.find(f);
  if (it == loop_descriptors_.end()) {
    return &loop_descriptors_
                .emplace(std::make_pair(f, LoopDescriptor(this, f)))
                .first->second;
  }

  return &it->second;
}

uint32_t IRContext::FindBuiltinInputVar(uint32_t builtin) {
  for (auto& a : module_->annotations()) {
    if (a.opcode() != SpvOpDecorate) continue;
    if (a.GetSingleWordInOperand(kSpvDecorateDecorationInIdx) !=
        SpvDecorationBuiltIn)
      continue;
    if (a.GetSingleWordInOperand(kSpvDecorateBuiltinInIdx) != builtin) continue;
    uint32_t target_id = a.GetSingleWordInOperand(kSpvDecorateTargetIdInIdx);
    Instruction* b_var = get_def_use_mgr()->GetDef(target_id);
    if (b_var->opcode() != SpvOpVariable) continue;
    if (b_var->GetSingleWordInOperand(0) != SpvStorageClassInput) continue;
    return target_id;
  }
  return 0;
}

void IRContext::AddVarToEntryPoints(uint32_t var_id) {
  uint32_t ocnt = 0;
  for (auto& e : module()->entry_points()) {
    bool found = false;
    e.ForEachInOperand([&ocnt, &found, &var_id](const uint32_t* idp) {
      if (ocnt >= kEntryPointInterfaceInIdx) {
        if (*idp == var_id) found = true;
      }
      ++ocnt;
    });
    if (!found) {
      e.AddOperand({SPV_OPERAND_TYPE_ID, {var_id}});
      get_def_use_mgr()->AnalyzeInstDefUse(&e);
    }
  }
}

uint32_t IRContext::GetBuiltinInputVarId(uint32_t builtin) {
  if (!AreAnalysesValid(kAnalysisBuiltinVarId)) ResetBuiltinAnalysis();
  // If cached, return it.
  std::unordered_map<uint32_t, uint32_t>::iterator it =
      builtin_var_id_map_.find(builtin);
  if (it != builtin_var_id_map_.end()) return it->second;
  // Look for one in shader
  uint32_t var_id = FindBuiltinInputVar(builtin);
  if (var_id == 0) {
    // If not found, create it
    // TODO(greg-lunarg): Add support for all builtins
    analysis::TypeManager* type_mgr = get_type_mgr();
    analysis::Type* reg_type;
    switch (builtin) {
      case SpvBuiltInFragCoord: {
        analysis::Float float_ty(32);
        analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
        analysis::Vector v4float_ty(reg_float_ty, 4);
        reg_type = type_mgr->GetRegisteredType(&v4float_ty);
        break;
      }
      case SpvBuiltInVertexIndex:
      case SpvBuiltInInstanceIndex:
      case SpvBuiltInPrimitiveId:
      case SpvBuiltInInvocationId:
      case SpvBuiltInSubgroupLocalInvocationId: {
        analysis::Integer uint_ty(32, false);
        reg_type = type_mgr->GetRegisteredType(&uint_ty);
        break;
      }
      case SpvBuiltInGlobalInvocationId:
      case SpvBuiltInLaunchIdNV: {
        analysis::Integer uint_ty(32, false);
        analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
        analysis::Vector v3uint_ty(reg_uint_ty, 3);
        reg_type = type_mgr->GetRegisteredType(&v3uint_ty);
        break;
      }
      case SpvBuiltInTessCoord: {
        analysis::Float float_ty(32);
        analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty);
        analysis::Vector v3float_ty(reg_float_ty, 3);
        reg_type = type_mgr->GetRegisteredType(&v3float_ty);
        break;
      }
      case SpvBuiltInSubgroupLtMask: {
        analysis::Integer uint_ty(32, false);
        analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
        analysis::Vector v4uint_ty(reg_uint_ty, 4);
        reg_type = type_mgr->GetRegisteredType(&v4uint_ty);
        break;
      }
      default: {
        assert(false && "unhandled builtin");
        return 0;
      }
    }
    uint32_t type_id = type_mgr->GetTypeInstruction(reg_type);
    uint32_t varTyPtrId =
        type_mgr->FindPointerToType(type_id, SpvStorageClassInput);
    // TODO(1841): Handle id overflow.
    var_id = TakeNextId();
    std::unique_ptr<Instruction> newVarOp(
        new Instruction(this, SpvOpVariable, varTyPtrId, var_id,
                        {{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
                          {SpvStorageClassInput}}}));
    get_def_use_mgr()->AnalyzeInstDefUse(&*newVarOp);
    module()->AddGlobalValue(std::move(newVarOp));
    get_decoration_mgr()->AddDecorationVal(var_id, SpvDecorationBuiltIn,
                                           builtin);
    AddVarToEntryPoints(var_id);
  }
  builtin_var_id_map_[builtin] = var_id;
  return var_id;
}

void IRContext::AddCalls(const Function* func, std::queue<uint32_t>* todo) {
  for (auto bi = func->begin(); bi != func->end(); ++bi)
    for (auto ii = bi->begin(); ii != bi->end(); ++ii)
      if (ii->opcode() == SpvOpFunctionCall)
        todo->push(ii->GetSingleWordInOperand(0));
}

bool IRContext::ProcessEntryPointCallTree(ProcessFunction& pfn) {
  // Collect all of the entry points as the roots.
  std::queue<uint32_t> roots;
  for (auto& e : module()->entry_points()) {
    roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));
  }
  return ProcessCallTreeFromRoots(pfn, &roots);
}

bool IRContext::ProcessReachableCallTree(ProcessFunction& pfn) {
  std::queue<uint32_t> roots;

  // Add all entry points since they can be reached from outside the module.
  for (auto& e : module()->entry_points())
    roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx));

  // Add all exported functions since they can be reached from outside the
  // module.
  for (auto& a : annotations()) {
    // TODO: Handle group decorations as well.  Currently not generate by any
    // front-end, but could be coming.
    if (a.opcode() == SpvOp::SpvOpDecorate) {
      if (a.GetSingleWordOperand(1) ==
          SpvDecoration::SpvDecorationLinkageAttributes) {
        uint32_t lastOperand = a.NumOperands() - 1;
        if (a.GetSingleWordOperand(lastOperand) ==
            SpvLinkageType::SpvLinkageTypeExport) {
          uint32_t id = a.GetSingleWordOperand(0);
          if (GetFunction(id)) {
            roots.push(id);
          }
        }
      }
    }
  }

  return ProcessCallTreeFromRoots(pfn, &roots);
}

bool IRContext::ProcessCallTreeFromRoots(ProcessFunction& pfn,
                                         std::queue<uint32_t>* roots) {
  // Process call tree
  bool modified = false;
  std::unordered_set<uint32_t> done;

  while (!roots->empty()) {
    const uint32_t fi = roots->front();
    roots->pop();
    if (done.insert(fi).second) {
      Function* fn = GetFunction(fi);
      assert(fn && "Trying to process a function that does not exist.");
      modified = pfn(fn) || modified;
      AddCalls(fn, roots);
    }
  }
  return modified;
}

void IRContext::EmitErrorMessage(std::string message, Instruction* inst) {
  if (!consumer()) {
    return;
  }

  Instruction* line_inst = inst;
  while (line_inst != nullptr) {  // Stop at the beginning of the basic block.
    if (!line_inst->dbg_line_insts().empty()) {
      line_inst = &line_inst->dbg_line_insts().back();
      if (line_inst->opcode() == SpvOpNoLine) {
        line_inst = nullptr;
      }
      break;
    }
    line_inst = line_inst->PreviousNode();
  }

  uint32_t line_number = 0;
  uint32_t col_number = 0;
  char* source = nullptr;
  if (line_inst != nullptr) {
    Instruction* file_name =
        get_def_use_mgr()->GetDef(line_inst->GetSingleWordInOperand(0));
    source = reinterpret_cast<char*>(&file_name->GetInOperand(0).words[0]);

    // Get the line number and column number.
    line_number = line_inst->GetSingleWordInOperand(1);
    col_number = line_inst->GetSingleWordInOperand(2);
  }

  message +=
      "\n  " + inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
  consumer()(SPV_MSG_ERROR, source, {line_number, col_number, 0},
             message.c_str());
}

// Gets the dominator analysis for function |f|.
DominatorAnalysis* IRContext::GetDominatorAnalysis(const Function* f) {
  if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) {
    ResetDominatorAnalysis();
  }

  if (dominator_trees_.find(f) == dominator_trees_.end()) {
    dominator_trees_[f].InitializeTree(*cfg(), f);
  }

  return &dominator_trees_[f];
}

// Gets the postdominator analysis for function |f|.
PostDominatorAnalysis* IRContext::GetPostDominatorAnalysis(const Function* f) {
  if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) {
    ResetDominatorAnalysis();
  }

  if (post_dominator_trees_.find(f) == post_dominator_trees_.end()) {
    post_dominator_trees_[f].InitializeTree(*cfg(), f);
  }

  return &post_dominator_trees_[f];
}

bool IRContext::CheckCFG() {
  std::unordered_map<uint32_t, std::vector<uint32_t>> real_preds;
  if (!AreAnalysesValid(kAnalysisCFG)) {
    return true;
  }

  for (Function& function : *module()) {
    for (const auto& bb : function) {
      bb.ForEachSuccessorLabel([&bb, &real_preds](const uint32_t lab_id) {
        real_preds[lab_id].push_back(bb.id());
      });
    }

    for (auto& bb : function) {
      std::vector<uint32_t> preds = cfg()->preds(bb.id());
      std::vector<uint32_t> real = real_preds[bb.id()];
      std::sort(preds.begin(), preds.end());
      std::sort(real.begin(), real.end());

      bool same = true;
      if (preds.size() != real.size()) {
        same = false;
      }

      for (size_t i = 0; i < real.size() && same; i++) {
        if (preds[i] != real[i]) {
          same = false;
        }
      }

      if (!same) {
        std::cerr << "Predecessors for " << bb.id() << " are different:\n";

        std::cerr << "Real:";
        for (uint32_t i : real) {
          std::cerr << ' ' << i;
        }
        std::cerr << std::endl;

        std::cerr << "Recorded:";
        for (uint32_t i : preds) {
          std::cerr << ' ' << i;
        }
        std::cerr << std::endl;
      }
      if (!same) return false;
    }
  }

  return true;
}
}  // namespace opt
}  // namespace spvtools
