// Copyright (c) 2015-2016 The Khronos Group 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.

#ifndef SOURCE_VAL_VALIDATION_STATE_H_
#define SOURCE_VAL_VALIDATION_STATE_H_

#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "source/assembly_grammar.h"
#include "source/diagnostic.h"
#include "source/disassemble.h"
#include "source/enum_set.h"
#include "source/latest_version_spirv_header.h"
#include "source/name_mapper.h"
#include "source/spirv_definition.h"
#include "source/spirv_validator_options.h"
#include "source/table2.h"
#include "source/val/decoration.h"
#include "source/val/function.h"
#include "source/val/instruction.h"
#include "spirv-tools/libspirv.h"

namespace spvtools {
namespace val {

/// This enum represents the sections of a SPIRV module. See section 2.4
/// of the SPIRV spec for additional details of the order. The enumerant values
/// are in the same order as the vector returned by GetModuleOrder
enum ModuleLayoutSection {
  kLayoutCapabilities,             /// < Section 2.4 #1
  kLayoutExtensions,               /// < Section 2.4 #2
  kLayoutExtInstImport,            /// < Section 2.4 #3
  kLayoutMemoryModel,              /// < Section 2.4 #4
  kLayoutSamplerImageAddressMode,  /// < Section 2.4 #5
                                   /// (SPV_NV_bindless_texture)
  kLayoutEntryPoint,               /// < Section 2.4 #6
  kLayoutExecutionMode,            /// < Section 2.4 #7
  kLayoutDebug1,                   /// < Section 2.4 #8 > 1
  kLayoutDebug2,                   /// < Section 2.4 #8 > 2
  kLayoutDebug3,                   /// < Section 2.4 #8 > 3
  kLayoutAnnotations,              /// < Section 2.4 #9
  kLayoutTypes,                    /// < Section 2.4 #10
  kLayoutFunctionDeclarations,     /// < Section 2.4 #11
  kLayoutFunctionDefinitions,      /// < Section 2.4 #12
  kLayoutGraphDefinitions          /// < Section 2.4 #13 (SPV_ARM_graph)
};

/// This enum represents the regions of a graph definition. The relative
/// ordering of the values is significant.
enum GraphDefinitionRegion {
  kGraphDefinitionOutside,
  kGraphDefinitionBegin,
  kGraphDefinitionInputs,
  kGraphDefinitionBody,
  kGraphDefinitionOutputs,
};

/// This class manages the state of the SPIR-V validation as it is being parsed.
class ValidationState_t {
 public:
  // Features that can optionally be turned on by a capability or environment.
  struct Feature {
    bool declare_int16_type = false;     // Allow OpTypeInt with 16 bit width?
    bool declare_float16_type = false;   // Allow OpTypeFloat with 16 bit width?
    bool declare_float8_type = false;    // Allow OpTypeFloat with 8 bit width?
    bool free_fp_rounding_mode = false;  // Allow the FPRoundingMode decoration
                                         // and its values to be used without
                                         // requiring any capability

    // Allow functionalities enabled by VariablePointers or
    // VariablePointersStorageBuffer capability.
    bool variable_pointers = false;

    // Permit group oerations Reduce, InclusiveScan, ExclusiveScan
    bool group_ops_reduce_and_scans = false;

    // Allow OpTypeInt with 8 bit width?
    bool declare_int8_type = false;

    // Target environment uses relaxed block layout.
    // This is true for Vulkan 1.1 or later.
    bool env_relaxed_block_layout = false;

    // Allow an OpTypeInt with 8 bit width to be used in more than just int
    // conversion opcodes
    bool use_int8_type = false;

    // SPIR-V 1.4 allows us to select between any two composite values
    // of the same type.
    bool select_between_composites = false;

    // SPIR-V 1.4 allows two memory access operands for OpCopyMemory and
    // OpCopyMemorySized.
    bool copy_memory_permits_two_memory_accesses = false;

    // SPIR-V 1.4 allows UConvert as a spec constant op in any environment.
    // The Kernel capability already enables it, separately from this flag.
    bool uconvert_spec_constant_op = false;

    // SPIR-V 1.4 allows Function and Private variables to be NonWritable
    bool nonwritable_var_in_function_or_private = false;

    // Whether LocalSizeId execution mode is allowed by the environment.
    bool env_allow_localsizeid = false;
  };

  ValidationState_t(const spv_const_context context,
                    const spv_const_validator_options opt,
                    const uint32_t* words, const size_t num_words,
                    const uint32_t max_warnings);

  /// Returns the context
  spv_const_context context() const { return context_; }

  /// Returns the command line options
  spv_const_validator_options options() const { return options_; }

  /// Sets the ID of the generator for this module.
  void setGenerator(uint32_t gen) { generator_ = gen; }

  /// Returns the ID of the generator for this module.
  uint32_t generator() const { return generator_; }

  /// Sets the SPIR-V version of this module.
  void setVersion(uint32_t ver) { version_ = ver; }

  /// Gets the SPIR-V version of this module.
  uint32_t version() const { return version_; }

  /// Forward declares the id in the module
  spv_result_t ForwardDeclareId(uint32_t id);

  /// Removes a forward declared ID if it has been defined
  spv_result_t RemoveIfForwardDeclared(uint32_t id);

  /// Registers an ID as a forward pointer
  spv_result_t RegisterForwardPointer(uint32_t id);

  /// Returns whether or not an ID is a forward pointer
  bool IsForwardPointer(uint32_t id) const;

  /// Assigns a name to an ID
  void AssignNameToId(uint32_t id, std::string name);

  /// Returns a string representation of the ID in the format <id>[Name] where
  /// the <id> is the numeric valid of the id and the Name is a name assigned by
  /// the OpName instruction
  std::string getIdName(uint32_t id) const;

  /// Accessor function for ID bound.
  uint32_t getIdBound() const;

  /// Mutator function for ID bound.
  void setIdBound(uint32_t bound);

  /// Returns the number of ID which have been forward referenced but not
  /// defined
  size_t unresolved_forward_id_count() const;

  /// Returns a vector of unresolved forward ids.
  std::vector<uint32_t> UnresolvedForwardIds() const;

  /// Returns true if the id has been defined
  bool IsDefinedId(uint32_t id) const;

  /// Increments the total number of instructions in the file.
  void increment_total_instructions() { total_instructions_++; }

  /// Increments the total number of functions in the file.
  void increment_total_functions() { total_functions_++; }

  /// Allocates internal storage. Note, calling this will invalidate any
  /// pointers to |ordered_instructions_| or |module_functions_| and, hence,
  /// should only be called at the beginning of validation.
  void preallocateStorage();

  /// Returns the current layout section which is being processed
  ModuleLayoutSection current_layout_section() const;

  /// Increments the module_layout_order_section_
  void ProgressToNextLayoutSectionOrder();

  /// Determines if the op instruction is in a previous layout section
  bool IsOpcodeInPreviousLayoutSection(spv::Op op);

  /// Determines if the op instruction is part of the current section
  bool IsOpcodeInCurrentLayoutSection(spv::Op op);

  DiagnosticStream diag(spv_result_t error_code, const Instruction* inst);

  /// Returns the function states
  std::vector<Function>& functions();

  /// Returns the function states
  Function& current_function();
  const Function& current_function() const;

  /// Returns function state with the given id, or nullptr if no such function.
  const Function* function(uint32_t id) const;
  Function* function(uint32_t id);

  /// Returns true if the called after a function instruction but before the
  /// function end instruction
  bool in_function_body() const;

  /// Returns true if called after a label instruction but before a branch
  /// instruction
  bool in_block() const;

  /// Returns the region of a graph definition we are in.
  GraphDefinitionRegion graph_definition_region() const;

  struct EntryPointDescription {
    std::string name;
    std::vector<uint32_t> interfaces;
  };

  /// Registers |id| as an entry point with |execution_model| and |interfaces|.
  void RegisterEntryPoint(const uint32_t id,
                          spv::ExecutionModel execution_model,
                          EntryPointDescription&& desc) {
    entry_points_.push_back(id);
    entry_point_to_execution_models_[id].insert(execution_model);
    entry_point_descriptions_[id].emplace_back(desc);
  }

  /// Returns a list of entry point function ids
  const std::vector<uint32_t>& entry_points() const { return entry_points_; }

  /// Returns the set of entry points that root call graphs that contain
  /// recursion.
  const std::set<uint32_t>& recursive_entry_points() const {
    return recursive_entry_points_;
  }

  /// Registers execution mode for the given entry point.
  void RegisterExecutionModeForEntryPoint(uint32_t entry_point,
                                          spv::ExecutionMode execution_mode) {
    entry_point_to_execution_modes_[entry_point].insert(execution_mode);
  }

  /// Registers that the entry point declares its local size
  void RegisterEntryPointLocalSize(uint32_t entry_point,
                                   const Instruction* inst) {
    entry_point_to_local_size_or_id_[entry_point] = inst;
  }

  /// Registers that the entry point maximum number of primitives
  /// mesh shader will ever emit
  void RegisterEntryPointOutputPrimitivesEXT(uint32_t entry_point,
                                             const Instruction* inst) {
    entry_point_to_output_primitives_[entry_point] = inst;
  }

  /// Returns the maximum number of primitives mesh shader can emit
  uint32_t GetOutputPrimitivesEXT(uint32_t entry_point) const {
    auto entry = entry_point_to_output_primitives_.find(entry_point);
    if (entry != entry_point_to_output_primitives_.end()) {
      auto inst = entry->second;
      return inst->GetOperandAs<uint32_t>(2);
    }
    return 0;
  }

  /// Registers that the entry point maximum number of vertices
  /// mesh shader will ever emit
  void RegisterEntryPointOutputVertices(uint32_t entry_point,
                                        const Instruction* inst) {
    entry_point_to_output_vertices_[entry_point] = inst;
  }

  /// Returns the maximum number of primitives mesh shader can emit
  uint32_t GetOutputVertices(uint32_t entry_point) const {
    auto entry = entry_point_to_output_vertices_.find(entry_point);
    if (entry != entry_point_to_output_vertices_.end()) {
      auto inst = entry->second;
      return inst->GetOperandAs<uint32_t>(2);
    }
    return 0;
  }

  /// Returns whether the entry point declares its local size
  bool EntryPointHasLocalSizeOrId(uint32_t entry_point) const {
    return entry_point_to_local_size_or_id_.find(entry_point) !=
           entry_point_to_local_size_or_id_.end();
  }
  /// Returns the id of the local size
  const Instruction* EntryPointLocalSizeOrId(uint32_t entry_point) const {
    return entry_point_to_local_size_or_id_.find(entry_point)->second;
  }

  /// Returns the interface descriptions of a given entry point.
  const std::vector<EntryPointDescription>& entry_point_descriptions(
      uint32_t entry_point) {
    return entry_point_descriptions_.at(entry_point);
  }

  /// Returns Execution Models for the given Entry Point.
  /// Returns nullptr if none found (would trigger assertion).
  const std::set<spv::ExecutionModel>* GetExecutionModels(
      uint32_t entry_point) const {
    const auto it = entry_point_to_execution_models_.find(entry_point);
    if (it == entry_point_to_execution_models_.end()) {
      assert(0);
      return nullptr;
    }
    return &it->second;
  }

  /// Returns Execution Modes for the given Entry Point.
  /// Returns nullptr if none found.
  const std::set<spv::ExecutionMode>* GetExecutionModes(
      uint32_t entry_point) const {
    const auto it = entry_point_to_execution_modes_.find(entry_point);
    if (it == entry_point_to_execution_modes_.end()) {
      return nullptr;
    }
    return &it->second;
  }

  /// Traverses call tree and computes function_to_entry_points_.
  /// Note: called after fully parsing the binary.
  void ComputeFunctionToEntryPointMapping();

  /// Traverse call tree and computes recursive_entry_points_.
  /// Note: called after fully parsing the binary and calling
  /// ComputeFunctionToEntryPointMapping.
  void ComputeRecursiveEntryPoints();

  /// Registers |id| as a graph entry point.
  void RegisterGraphEntryPoint(const uint32_t id) {
    graph_entry_points_.push_back(id);
  }

  /// Returns a list of graph entry point graph ids
  const std::vector<uint32_t>& graph_entry_points() const {
    return graph_entry_points_;
  }

  /// Returns all the entry points that can call |func|.
  const std::vector<uint32_t>& FunctionEntryPoints(uint32_t func) const;

  /// Returns all the entry points that statically use |id|.
  ///
  /// Note: requires ComputeFunctionToEntryPointMapping to have been called.
  std::set<uint32_t> EntryPointReferences(uint32_t id) const;

  /// Inserts an <id> to the set of functions that are target of OpFunctionCall.
  void AddFunctionCallTarget(const uint32_t id) {
    function_call_targets_.insert(id);
    current_function().AddFunctionCallTarget(id);
  }

  /// Returns whether or not a function<id> is the target of OpFunctionCall.
  bool IsFunctionCallTarget(const uint32_t id) {
    return (function_call_targets_.find(id) != function_call_targets_.end());
  }

  bool IsFunctionCallDefined(const uint32_t id) {
    return (id_to_function_.find(id) != id_to_function_.end());
  }
  /// Registers the capability and its dependent capabilities
  void RegisterCapability(spv::Capability cap);

  /// Registers the extension.
  void RegisterExtension(Extension ext);

  /// Registers the function in the module. Subsequent instructions will be
  /// called against this function
  spv_result_t RegisterFunction(uint32_t id, uint32_t ret_type_id,
                                spv::FunctionControlMask function_control,
                                uint32_t function_type_id);

  /// Register a function end instruction
  spv_result_t RegisterFunctionEnd();

  /// Sets the region of a graph definition we're in.
  void SetGraphDefinitionRegion(GraphDefinitionRegion region);

  /// Returns true if the capability is enabled in the module.
  bool HasCapability(spv::Capability cap) const {
    return module_capabilities_.contains(cap);
  }

  /// Returns a reference to the set of capabilities in the module.
  /// This is provided for debuggability.
  const CapabilitySet& module_capabilities() const {
    return module_capabilities_;
  }

  /// Returns true if the extension is enabled in the module.
  bool HasExtension(Extension ext) const {
    return module_extensions_.contains(ext);
  }

  /// Returns true if any of the capabilities is enabled, or if |capabilities|
  /// is an empty set.
  bool HasAnyOfCapabilities(const CapabilitySet& capabilities) const;

  /// Returns true if any of the extensions is enabled, or if |extensions|
  /// is an empty set.
  bool HasAnyOfExtensions(const ExtensionSet& extensions) const;

  /// Sets the addressing model of this module (logical/physical).
  void set_addressing_model(spv::AddressingModel am);

  /// Returns true if the OpMemoryModel was found.
  bool has_memory_model_specified() const {
    return addressing_model_ != spv::AddressingModel::Max &&
           memory_model_ != spv::MemoryModel::Max;
  }

  /// Returns the addressing model of this module, or Logical if uninitialized.
  spv::AddressingModel addressing_model() const;

  /// Returns the addressing model of this module, or Logical if uninitialized.
  uint32_t pointer_size_and_alignment() const {
    return pointer_size_and_alignment_;
  }

  /// Sets the memory model of this module.
  void set_memory_model(spv::MemoryModel mm);

  /// Returns the memory model of this module, or Simple if uninitialized.
  spv::MemoryModel memory_model() const;

  /// Sets the bit width for sampler/image type variables. If not set, they are
  /// considered opaque
  void set_samplerimage_variable_address_mode(uint32_t bit_width);

  /// Get the addressing mode currently set. If 0, it means addressing mode is
  /// invalid Sampler/Image type variables must be considered opaque This mode
  /// is only valid after the instruction has been read
  uint32_t samplerimage_variable_address_mode() const;

  /// Returns true if the OpSamplerImageAddressingModeNV was found.
  bool has_samplerimage_variable_address_mode_specified() const {
    return sampler_image_addressing_mode_ != 0;
  }

  const AssemblyGrammar& grammar() const { return grammar_; }

  /// Inserts the instruction into the list of ordered instructions in the file.
  Instruction* AddOrderedInstruction(const spv_parsed_instruction_t* inst);

  /// Registers the instruction. This will add the instruction to the list of
  /// definitions and register sampled image consumers.
  void RegisterInstruction(Instruction* inst);

  /// Registers the debug instruction information.
  void RegisterDebugInstruction(const Instruction* inst);

  /// Registers the decoration for the given <id>
  void RegisterDecorationForId(uint32_t id, const Decoration& dec) {
    auto& dec_list = id_decorations_[id];
    dec_list.insert(dec);
  }

  /// Registers the list of decorations for the given <id>
  template <class InputIt>
  void RegisterDecorationsForId(uint32_t id, InputIt begin, InputIt end) {
    std::set<Decoration>& cur_decs = id_decorations_[id];
    cur_decs.insert(begin, end);
  }

  /// Registers the list of decorations for the given member of the given
  /// structure.
  template <class InputIt>
  void RegisterDecorationsForStructMember(uint32_t struct_id,
                                          uint32_t member_index, InputIt begin,
                                          InputIt end) {
    std::set<Decoration>& cur_decs = id_decorations_[struct_id];
    for (InputIt iter = begin; iter != end; ++iter) {
      Decoration dec = *iter;
      dec.set_struct_member_index(member_index);
      cur_decs.insert(dec);
    }
  }

  /// Returns all the decorations for the given <id>. If no decorations exist
  /// for the <id>, it registers an empty set for it in the map and
  /// returns the empty set.
  std::set<Decoration>& id_decorations(uint32_t id) {
    return id_decorations_[id];
  }

  /// Returns the range of decorations for the given field of the given <id>.
  struct FieldDecorationsIter {
    std::set<Decoration>::const_iterator begin;
    std::set<Decoration>::const_iterator end;
  };
  FieldDecorationsIter id_member_decorations(uint32_t id,
                                             uint32_t member_index) {
    const auto& decorations = id_decorations_[id];

    // The decorations are sorted by member_index, so this look up will give the
    // exact range of decorations for this member index.
    Decoration min_decoration((spv::Decoration)0, {}, member_index);
    Decoration max_decoration(spv::Decoration::Max, {}, member_index);

    FieldDecorationsIter result;
    result.begin = decorations.lower_bound(min_decoration);
    result.end = decorations.upper_bound(max_decoration);

    return result;
  }

  // Returns const pointer to the internal decoration container.
  const std::map<uint32_t, std::set<Decoration>>& id_decorations() const {
    return id_decorations_;
  }

  /// Returns true if the given id <id> has the given decoration <dec>,
  /// otherwise returns false.
  bool HasDecoration(uint32_t id, spv::Decoration dec) {
    const auto& decorations = id_decorations_.find(id);
    if (decorations == id_decorations_.end()) return false;

    return std::any_of(
        decorations->second.begin(), decorations->second.end(),
        [dec](const Decoration& d) { return dec == d.dec_type(); });
  }

  /// Returns true if the given id <id> has the given built-in decoration <bt>,
  /// otherwise returns false.
  bool IsBuiltin(spv::Id id, spv::BuiltIn bt) {
    for (auto& dec : id_decorations(id)) {
      if (dec.dec_type() == spv::Decoration::BuiltIn) {
        if (dec.builtin() == bt) return true;
        break;
      }
    }
    return false;
  }

  bool ContainsBuiltin(spv::Id id, spv::BuiltIn bt) {
    const auto isHeapType = [&](const Instruction* inst) {
      if (HasCapability(spv::Capability::DescriptorHeapEXT) &&
          IsBuiltin(inst->id(), bt)) {
        return true;
      }
      return false;
    };
    return ContainsType(uint32_t(id), isHeapType);
  }

  /// Finds id's def, if it exists.  If found, returns the definition otherwise
  /// nullptr
  const Instruction* FindDef(uint32_t id) const;

  /// Finds id's def, if it exists.  If found, returns the definition otherwise
  /// nullptr
  Instruction* FindDef(uint32_t id);

  /// Returns the instructions in the order they appear in the binary
  const std::vector<Instruction>& ordered_instructions() const {
    return ordered_instructions_;
  }

  /// Returns a map of instructions mapped by their result id
  const std::unordered_map<uint32_t, Instruction*>& all_definitions() const {
    return all_definitions_;
  }

  /// Returns a vector containing the instructions that consume the given
  /// SampledImage id.
  std::vector<Instruction*> getSampledImageConsumers(uint32_t id) const;

  /// Records cons_id as a consumer of sampled_image_id.
  void RegisterSampledImageConsumer(uint32_t sampled_image_id,
                                    Instruction* consumer);

  // Record a cons_id as a consumer of texture_id
  // if texture 'texture_id' has a QCOM image processing decoration
  // and consumer is a load or a sampled image instruction
  void RegisterQCOMImageProcessingTextureConsumer(uint32_t texture_id,
                                                  const Instruction* consumer0,
                                                  const Instruction* consumer1);

  // Record a function's storage class consumer instruction
  void RegisterStorageClassConsumer(spv::StorageClass storage_class,
                                    Instruction* consumer);

  /// Returns the set of Global Variables.
  std::unordered_set<uint32_t>& global_vars() { return global_vars_; }

  /// Returns the set of Local Variables.
  std::unordered_set<uint32_t>& local_vars() { return local_vars_; }

  /// Returns the number of Global Variables.
  size_t num_global_vars() { return global_vars_.size(); }

  /// Returns the number of Local Variables.
  size_t num_local_vars() { return local_vars_.size(); }

  /// Inserts a new <id> to the set of Global Variables.
  void registerGlobalVariable(const uint32_t id) { global_vars_.insert(id); }

  /// Inserts a new <id> to the set of Local Variables.
  void registerLocalVariable(const uint32_t id) { local_vars_.insert(id); }

  // Returns true if using relaxed block layout, equivalent to
  // VK_KHR_relaxed_block_layout.
  bool IsRelaxedBlockLayout() const {
    return features_.env_relaxed_block_layout || options()->relax_block_layout;
  }

  // Returns true if allowing localsizeid, either because the environment always
  // allows it, or because it is enabled from the command-line.
  bool IsLocalSizeIdAllowed() const {
    return features_.env_allow_localsizeid || options()->allow_localsizeid;
  }

  /// Sets the struct nesting depth for a given struct ID
  void set_struct_nesting_depth(uint32_t id, uint32_t depth) {
    struct_nesting_depth_[id] = depth;
  }

  /// Returns the nesting depth of a given structure ID
  uint32_t struct_nesting_depth(uint32_t id) {
    return struct_nesting_depth_[id];
  }

  /// Records the has a nested block/bufferblock decorated struct for a given
  /// struct ID
  void SetHasNestedBlockOrBufferBlockStruct(uint32_t id, bool has) {
    struct_has_nested_blockorbufferblock_struct_[id] = has;
  }

  /// For a given struct ID returns true if it has a nested block/bufferblock
  /// decorated struct
  bool GetHasNestedBlockOrBufferBlockStruct(uint32_t id) {
    return struct_has_nested_blockorbufferblock_struct_[id];
  }

  /// Records that the structure type has a member decorated with a built-in.
  void RegisterStructTypeWithBuiltInMember(uint32_t id) {
    builtin_structs_.insert(id);
  }

  /// Returns true if the struct type with the given Id has a BuiltIn member.
  bool IsStructTypeWithBuiltInMember(uint32_t id) const {
    return (builtin_structs_.find(id) != builtin_structs_.end());
  }

  // Returns the state of optional features.
  const Feature& features() const { return features_; }

  /// Adds the instruction data to unique_type_declarations_.
  /// Returns false if an identical type declaration already exists.
  bool RegisterUniqueTypeDeclaration(const Instruction* inst);

  // Returns type_id of the scalar component of |id|.
  // |id| can be either
  // - scalar, vector or matrix type
  // - object of either scalar, vector or matrix type
  uint32_t GetComponentType(uint32_t id) const;

  // Returns
  // - 1 for scalar types or objects
  // - vector size for vector types or objects
  // - num columns for matrix types or objects
  // Should not be called with any other arguments (will return zero and invoke
  // assertion).
  uint32_t GetDimension(uint32_t id) const;

  // Returns bit width of scalar or component.
  // |id| can be
  // - scalar, vector or matrix type
  // - object of either scalar, vector or matrix type
  // Will invoke assertion and return 0 if |id| is none of the above.
  uint32_t GetBitWidth(uint32_t id) const;

  // Provides detailed information on matrix type.
  // Returns false iff |id| is not matrix type.
  bool GetMatrixTypeInfo(uint32_t id, uint32_t* num_rows, uint32_t* num_cols,
                         uint32_t* column_type, uint32_t* component_type) const;

  // Collects struct member types into |member_types|.
  // Returns false iff not struct type or has no members.
  // Deletes prior contents of |member_types|.
  bool GetStructMemberTypes(uint32_t struct_type_id,
                            std::vector<uint32_t>* member_types) const;

  // Returns true if |id| is a type corresponding to the name of the function.
  // Only works for types not for objects.
  bool IsVoidType(uint32_t id) const;
  bool IsScalarType(uint32_t id) const;
  bool IsVectorType(uint32_t id) const;
  bool IsBfloat16ScalarType(uint32_t id) const;
  bool IsBfloat16VectorType(uint32_t id) const;
  bool IsBfloat16CoopMatType(uint32_t id) const;
  bool IsBfloat16Type(uint32_t id) const;
  bool IsFP8ScalarType(uint32_t id) const;
  bool IsFP8VectorType(uint32_t id) const;
  bool IsFP8CoopMatType(uint32_t id) const;
  bool IsFP8Type(uint32_t id) const;
  bool IsOCPMicroscalingScalarType(uint32_t id) const;
  bool IsOCPMicroscalingNonByteScalarType(uint32_t id) const;
  bool IsOCPMicroscalingType(uint32_t id) const;
  bool ContainsOCPMicroscalingType(uint32_t id) const;
  bool ContainsOCPMicroscalingNonByteType(uint32_t id) const;
  bool IsFloatScalarType(uint32_t id, uint32_t width = 0) const;
  bool IsFloatArrayType(uint32_t id) const;
  bool IsFloatVectorType(uint32_t id) const;
  bool IsFloat16Vector2Or4Type(uint32_t id) const;
  bool IsFloatScalarOrVectorType(uint32_t id) const;
  bool IsFloatMatrixType(uint32_t id) const;
  bool IsIntScalarType(uint32_t id, uint32_t width = 0) const;
  bool IsIntScalarTypeWithSignedness(uint32_t id, uint32_t signedness) const;
  bool IsIntVectorType(uint32_t id) const;
  bool IsIntScalarOrVectorType(uint32_t id) const;
  bool IsUnsignedIntScalarType(uint32_t id) const;
  bool IsUnsignedIntVectorType(uint32_t id) const;
  bool IsUnsignedIntScalarOrVectorType(uint32_t id) const;
  bool IsSignedIntScalarType(uint32_t id) const;
  bool IsSignedIntVectorType(uint32_t id) const;
  bool IsBoolScalarType(uint32_t id) const;
  bool IsBoolVectorType(uint32_t id) const;
  bool IsBoolScalarOrVectorType(uint32_t id) const;
  bool IsPointerType(uint32_t id) const;
  bool IsAccelerationStructureType(uint32_t id) const;
  bool IsCooperativeMatrixType(uint32_t id) const;
  bool IsCooperativeMatrixNVType(uint32_t id) const;
  bool IsCooperativeMatrixKHRType(uint32_t id) const;
  bool IsCooperativeMatrixAType(uint32_t id) const;
  bool IsCooperativeMatrixBType(uint32_t id) const;
  bool IsCooperativeMatrixAccType(uint32_t id) const;
  bool IsFloatCooperativeMatrixType(uint32_t id) const;
  bool IsIntCooperativeMatrixType(uint32_t id) const;
  bool IsUnsignedIntCooperativeMatrixType(uint32_t id) const;
  bool IsUnsigned64BitHandle(uint32_t id) const;
  bool IsCooperativeVectorNVType(uint32_t id) const;
  bool IsFloatCooperativeVectorNVType(uint32_t id) const;
  bool IsIntCooperativeVectorNVType(uint32_t id) const;
  bool IsUnsignedIntCooperativeVectorNVType(uint32_t id) const;
  bool IsTensorType(uint32_t id) const;
  bool IsDescriptorType(spv::Op opcode) const;
  bool IsDescriptorType(uint32_t id) const;
  // When |length| is not 0, return true only if the array length is equal to
  // |length| and the array length is not defined by a specialization constant.
  bool IsArrayType(uint32_t id, uint64_t length = 0) const;
  bool IsIntArrayType(uint32_t id, uint64_t length = 0) const;
  template <unsigned int N>
  bool IsIntNOrFP32OrFP16(unsigned int type_id) {
    return this->ContainsType(
        type_id,
        [](const Instruction* inst) {
          if (inst->opcode() == spv::Op::OpTypeInt) {
            return inst->GetOperandAs<uint32_t>(1) == N;
          } else if (inst->opcode() == spv::Op::OpTypeFloat) {
            if (inst->operands().size() > 2) {
              // Not IEEE
              return false;
            }
            auto width = inst->GetOperandAs<uint32_t>(1);
            return width == 32 || width == 16;
          }
          return false;
        },
        /* traverse_all_types = */ false);
  }

  // Will walk the type to find the largest scalar value size.
  // Returns value is in bytes.
  // This is designed to pass in the %type from a PSB pointer
  //   %ptr = OpTypePointer PhysicalStorageBuffer %type
  uint32_t GetLargestScalarType(uint32_t id) const;
  bool IsDescriptorHeapBaseVariable(const Instruction* inst);
  const Instruction* FindUntypedBaseVariable(const Instruction* inst);

  // Returns true if |id| is a type id that contains |type| (or integer or
  // floating point type) of |width| bits.
  bool ContainsSizedIntOrFloatType(uint32_t id, spv::Op type,
                                   uint32_t width) const;
  // Returns true if |id| is a type id that contains a 8- or 16-bit int or
  // 16-bit float that is not generally enabled for use.
  bool ContainsLimitedUseIntOrFloatType(uint32_t id) const;

  // Returns true if |id| is a type that contains a runtime-sized array.
  // Does not consider a pointers as contains the array.
  bool ContainsRuntimeArray(uint32_t id) const;

  // Generic type traversal.
  // Only traverse pointers and functions if |traverse_all_types| is true.
  // Recursively tests |f| against the type hierarchy headed by |id|.
  bool ContainsType(uint32_t id,
                    const std::function<bool(const Instruction*)>& f,
                    bool traverse_all_types = true) const;

  // Returns true if |id| is type id that contains an untyped pointer.
  bool ContainsUntypedPointer(uint32_t id) const;

  // Returns type_id if id has type or zero otherwise.
  uint32_t GetTypeId(uint32_t id) const;

  // Returns opcode of the instruction which issued the id or OpNop if the
  // instruction is not registered.
  spv::Op GetIdOpcode(uint32_t id) const;

  // Returns type_id for given id operand if it has a type or zero otherwise.
  // |operand_index| is expected to be pointing towards an operand which is an
  // id.
  uint32_t GetOperandTypeId(const Instruction* inst,
                            size_t operand_index) const;

  // Provides information on pointer type. Returns false iff not pointer type.
  bool GetPointerTypeInfo(uint32_t id, uint32_t* data_type,
                          spv::StorageClass* storage_class) const;

  // Returns the value assocated with id via 'value' if id is an OpConstant
  template <typename T>
  bool GetConstantValueAs(unsigned int id, T& value) {
    const auto inst = FindDef(id);
    uint64_t ui64_val = 0u;
    bool status = (inst && spvOpcodeIsConstant(inst->opcode()) &&
                   EvalConstantValUint64(id, &ui64_val));
    if (status == true) value = static_cast<T>(ui64_val);
    return status;
  }

  // Is the ID the type of a pointer to a uniform block: Block-decorated struct
  // in uniform storage class? The result is only valid after internal method
  // CheckDecorationsOfBuffers has been called.
  bool IsPointerToUniformBlock(uint32_t type_id) const {
    return pointer_to_uniform_block_.find(type_id) !=
           pointer_to_uniform_block_.cend();
  }
  // Save the ID of a pointer to uniform block.
  void RegisterPointerToUniformBlock(uint32_t type_id) {
    pointer_to_uniform_block_.insert(type_id);
  }
  // Is the ID the type of a struct used as a uniform block?
  // The result is only valid after internal method CheckDecorationsOfBuffers
  // has been called.
  bool IsStructForUniformBlock(uint32_t type_id) const {
    return struct_for_uniform_block_.find(type_id) !=
           struct_for_uniform_block_.cend();
  }
  // Save the ID of a struct of a uniform block.
  void RegisterStructForUniformBlock(uint32_t type_id) {
    struct_for_uniform_block_.insert(type_id);
  }
  // Is the ID the type of a pointer to a storage buffer: BufferBlock-decorated
  // struct in uniform storage class, or Block-decorated struct in StorageBuffer
  // storage class? The result is only valid after internal method
  // CheckDecorationsOfBuffers has been called.
  bool IsPointerToStorageBuffer(uint32_t type_id) const {
    return pointer_to_storage_buffer_.find(type_id) !=
           pointer_to_storage_buffer_.cend();
  }
  // Save the ID of a pointer to a storage buffer.
  void RegisterPointerToStorageBuffer(uint32_t type_id) {
    pointer_to_storage_buffer_.insert(type_id);
  }
  // Is the ID the type of a struct for storage buffer?
  // The result is only valid after internal method CheckDecorationsOfBuffers
  // has been called.
  bool IsStructForStorageBuffer(uint32_t type_id) const {
    return struct_for_storage_buffer_.find(type_id) !=
           struct_for_storage_buffer_.cend();
  }
  // Save the ID of a struct of a storage buffer.
  void RegisterStructForStorageBuffer(uint32_t type_id) {
    struct_for_storage_buffer_.insert(type_id);
  }

  // Is the ID the type of a pointer to a storage image?  That is, the pointee
  // type is an image type which is known to not use a sampler.
  bool IsPointerToStorageImage(uint32_t type_id) const {
    return pointer_to_storage_image_.find(type_id) !=
           pointer_to_storage_image_.cend();
  }
  // Save the ID of a pointer to a storage image.
  void RegisterPointerToStorageImage(uint32_t type_id) {
    pointer_to_storage_image_.insert(type_id);
  }

  // Is the ID the type of a pointer to a tensor?  That is, the pointee
  // type is a tensor type.
  bool IsPointerToTensor(uint32_t type_id) const {
    return pointer_to_tensor_.find(type_id) != pointer_to_tensor_.cend();
  }
  // Save the ID of a pointer to a tensor.
  void RegisterPointerToTensor(uint32_t type_id) {
    pointer_to_tensor_.insert(type_id);
  }

  // Tries to evaluate a any scalar integer OpConstant as uint64.
  // OpConstantNull is defined as zero for scalar int (will return true)
  // OpSpecConstant* return false since their values cannot be relied upon
  // during validation.
  bool EvalConstantValUint64(uint32_t id, uint64_t* val) const;
  // Same as EvalConstantValUint64 but returns a signed int
  bool EvalConstantValInt64(uint32_t id, int64_t* val) const;

  // Tries to evaluate a 32-bit signed or unsigned scalar integer constant.
  // Returns tuple <is_int32, is_const_int32, value>.
  // OpSpecConstant* return |is_const_int32| as false since their values cannot
  // be relied upon during validation.
  std::tuple<bool, bool, uint32_t> EvalInt32IfConst(uint32_t id) const;

  // Returns the disassembly string for the given instruction.
  std::string Disassemble(const Instruction& inst) const;

  // Returns the disassembly string for the given instruction.
  std::string Disassemble(const uint32_t* words, uint16_t num_words) const;

  // Returns the string name for |decoration|.
  std::string SpvDecorationString(uint32_t decoration) {
    const spvtools::OperandDesc* desc = nullptr;
    if (spvtools::LookupOperand(SPV_OPERAND_TYPE_DECORATION, decoration,
                                &desc) != SPV_SUCCESS) {
      return std::string("Unknown");
    }
    return std::string(desc->name().data());
  }
  std::string SpvDecorationString(spv::Decoration decoration) {
    return SpvDecorationString(uint32_t(decoration));
  }

  bool CheckForceOpacityMicromap2StateKHRCapabilityRequirement(
      const Instruction* inst, uint32_t flag_operand) {
    bool retval = true;
    uint64_t flag_val = 0;
    if (EvalConstantValUint64(inst->GetOperandAs<uint32_t>(flag_operand),
                              &flag_val)) {
      if ((flag_val & static_cast<uint64_t>(
                          spv::RayFlagsMask::ForceOpacityMicromap2StateKHR)) !=
          0) {
        assert(HasCapability(spv::Capability::RayQueryKHR) ||
               HasCapability(spv::Capability::RayTracingKHR));
        retval = HasCapability(spv::Capability::RayTracingOpacityMicromapKHR);
      }
    }
    return retval;
  }

  // Returns whether type result_type_id and type m2 are cooperative matrices
  // with the same "shape" (matching scope, rows, cols). If any are
  // specialization constants, we assume they can match because we can't prove
  // they don't.
  spv_result_t CooperativeMatrixShapesMatch(const Instruction* inst,
                                            uint32_t result_type_id,
                                            uint32_t m2, bool is_conversion,
                                            bool swap_row_col = false);

  spv_result_t CooperativeVectorDimensionsMatch(const Instruction* inst,
                                                uint32_t v1, uint32_t v2);

  // Returns true if |lhs| and |rhs| logically match and, if the decorations of
  // |rhs| are a subset of |lhs|.
  //
  // 1. Must both be either OpTypeArray or OpTypeStruct
  // 2. If OpTypeArray, then
  //  * Length must be the same
  //  * Element type must match or logically match
  // 3. If OpTypeStruct, then
  //  * Both have same number of elements
  //  * Element N for both structs must match or logically match
  //
  // If |check_decorations| is false, then the decorations are not checked.
  bool LogicallyMatch(const Instruction* lhs, const Instruction* rhs,
                      bool check_decorations);

  // Traces |inst| to find a single base pointer. Returns the base pointer.
  // Will trace through the following instructions:
  // * OpAccessChain
  // * OpInBoundsAccessChain
  // * OpPtrAccessChain
  // * OpInBoundsPtrAccessChain
  // * OpCopyObject
  const Instruction* TracePointer(const Instruction* inst) const;

  // Validates the storage class for the target environment.
  bool IsValidStorageClass(spv::StorageClass storage_class) const;

  // Helps formulate a mesesage to user that setting one of the validator
  // options might make their SPIR-V actually valid The |hint| option is because
  // some checks are intertwined with each other, so hard to give confirmation
  std::string MissingFeature(const std::string& feature,
                             const std::string& cmdline, bool hint) const;

  // Takes a Vulkan Valid Usage ID (VUID) as |id| and optional |reference| and
  // will return a non-empty string only if ID is known and targeting Vulkan.
  // VUIDs are found in the Vulkan-Docs repo in the form "[[VUID-ref-ref-id]]"
  // where "id" is always an 5 char long number (with zeros padding) and matches
  // to |id|. |reference| is used if there is a "common validity" and the VUID
  // shares the same |id| value.
  //
  // More details about Vulkan validation can be found in Vulkan Guide:
  // https://github.com/KhronosGroup/Vulkan-Guide/blob/master/chapters/validation_overview.md
  std::string VkErrorID(uint32_t id, const char* reference = nullptr) const;

  // Testing method to allow setting the current layout section.
  void SetCurrentLayoutSectionForTesting(ModuleLayoutSection section) {
    current_layout_section_ = section;
  }

  // Check if instruction 'id' is a consumer of a texture decorated
  // with a QCOM image processing decoration
  bool IsQCOMImageProcessingTextureConsumer(uint32_t id) {
    return qcom_image_processing_consumers_.find(id) !=
           qcom_image_processing_consumers_.end();
  }

  // Get the list of line lengths for a given result ID of a DebugSource
  // instruction Will create a new vector if DebugSource is not found
  std::vector<uint32_t>& GetDebugSourceLineLength(uint32_t id);

  void RegisterShaderDebugInfo(uint32_t id) { shader_debug_info_set_id = id; }
  uint32_t ShaderDebugInfoSet() const { return shader_debug_info_set_id; }
  std::string InspectShaderDebugInfo(const Instruction& inst);

 private:
  ValidationState_t(const ValidationState_t&);

  const spv_const_context context_;

  /// Stores the Validator command line options. Must be a valid options object.
  const spv_const_validator_options options_;

  /// The SPIR-V binary module we're validating.
  const uint32_t* words_;
  const size_t num_words_;

  /// The generator of the SPIR-V.
  uint32_t generator_ = 0;

  /// The version of the SPIR-V.
  uint32_t version_ = 0;

  /// The total number of instructions in the binary.
  size_t total_instructions_ = 0;
  /// The total number of functions in the binary.
  size_t total_functions_ = 0;

  /// IDs which have been forward declared but have not been defined
  std::unordered_set<uint32_t> unresolved_forward_ids_;

  /// IDs that have been declared as forward pointers.
  std::unordered_set<uint32_t> forward_pointer_ids_;

  /// Stores a vector of instructions that use the result of a given
  /// OpSampledImage instruction.
  std::unordered_map<uint32_t, std::vector<Instruction*>>
      sampled_image_consumers_;

  /// Stores load instructions that load textures used
  //  in QCOM image processing functions
  std::unordered_set<uint32_t> qcom_image_processing_consumers_;

  /// A map of operand IDs and their names defined by the OpName instruction
  std::unordered_map<uint32_t, std::string> operand_names_;

  /// The section of the code being processed
  ModuleLayoutSection current_layout_section_;

  /// A list of functions in the module.
  /// Pointers to objects in this container are guaranteed to be stable and
  /// valid until the end of lifetime of the validation state.
  std::vector<Function> module_functions_;

  /// Capabilities declared in the module
  CapabilitySet module_capabilities_;

  /// Extensions declared in the module
  ExtensionSet module_extensions_;

  /// List of all instructions in the order they appear in the binary
  std::vector<Instruction> ordered_instructions_;

  /// Instructions that can be referenced by Ids
  std::unordered_map<uint32_t, Instruction*> all_definitions_;

  /// IDs that are entry points, ie, arguments to OpEntryPoint.
  std::vector<uint32_t> entry_points_;

  /// Maps an entry point id to its descriptions.
  std::unordered_map<uint32_t, std::vector<EntryPointDescription>>
      entry_point_descriptions_;

  /// IDs that are entry points, ie, arguments to OpEntryPoint, and root a call
  /// graph that recurses.
  std::set<uint32_t> recursive_entry_points_;

  /// IDs that are graph entry points, ie, arguments to OpGraphEntryPointARM.
  std::vector<uint32_t> graph_entry_points_;

  /// Functions IDs that are target of OpFunctionCall.
  std::unordered_set<uint32_t> function_call_targets_;

  /// ID Bound from the Header
  uint32_t id_bound_;

  /// Set of Global Variable IDs (Storage Class other than 'Function')
  std::unordered_set<uint32_t> global_vars_;

  /// Set of Local Variable IDs ('Function' Storage Class)
  std::unordered_set<uint32_t> local_vars_;

  /// Set of struct types that have members with a BuiltIn decoration.
  std::unordered_set<uint32_t> builtin_structs_;

  /// Structure Nesting Depth
  std::unordered_map<uint32_t, uint32_t> struct_nesting_depth_;

  /// Structure has nested blockorbufferblock struct
  std::unordered_map<uint32_t, bool>
      struct_has_nested_blockorbufferblock_struct_;

  /// Stores the list of decorations for a given <id>
  std::map<uint32_t, std::set<Decoration>> id_decorations_;

  /// Stores type declarations which need to be unique (i.e. non-aggregates),
  /// in the form [opcode, operand words], result_id is not stored.
  /// Using ordered set to avoid the need for a vector hash function.
  /// The size of this container is expected not to exceed double-digits.
  std::set<std::vector<uint32_t>> unique_type_declarations_;

  AssemblyGrammar grammar_;

  spv::AddressingModel addressing_model_;
  spv::MemoryModel memory_model_;
  // pointer size derived from addressing model. Assumes all storage classes
  // have the same pointer size (for physical pointer types).
  uint32_t pointer_size_and_alignment_;

  /// bit width of sampler/image type variables. Valid values are 32 and 64
  uint32_t sampler_image_addressing_mode_;

  /// NOTE: See corresponding getter functions
  bool in_function_;

  /// Where in a graph definition we are
  /// NOTE: See corresponding getter/setter functions
  GraphDefinitionRegion graph_definition_region_;

  /// The state of optional features.  These are determined by capabilities
  /// declared by the module and the environment.
  Feature features_;

  /// Maps function ids to function stat objects.
  std::unordered_map<uint32_t, Function*> id_to_function_;

  /// Mapping entry point -> execution models. It is presumed that the same
  /// function could theoretically be used as 'main' by multiple OpEntryPoint
  /// instructions.
  std::unordered_map<uint32_t, std::set<spv::ExecutionModel>>
      entry_point_to_execution_models_;

  /// Mapping entry point -> execution modes.
  std::unordered_map<uint32_t, std::set<spv::ExecutionMode>>
      entry_point_to_execution_modes_;

  // Mapping entry point -> local size execution mode instruction
  std::unordered_map<uint32_t, const Instruction*>
      entry_point_to_local_size_or_id_;

  // Mapping entry point -> OutputPrimitivesEXT execution mode instruction
  std::unordered_map<uint32_t, const Instruction*>
      entry_point_to_output_primitives_;

  // Mapping entry point -> OutputVertices execution mode instruction
  std::unordered_map<uint32_t, const Instruction*>
      entry_point_to_output_vertices_;

  /// Mapping function -> array of entry points inside this
  /// module which can (indirectly) call the function.
  std::unordered_map<uint32_t, std::vector<uint32_t>> function_to_entry_points_;
  const std::vector<uint32_t> empty_ids_;

  // The IDs of types of pointers to Block-decorated structs in Uniform storage
  // class. This is populated at the start of ValidateDecorations.
  std::unordered_set<uint32_t> pointer_to_uniform_block_;
  // The IDs of struct types for uniform blocks.
  // This is populated at the start of ValidateDecorations.
  std::unordered_set<uint32_t> struct_for_uniform_block_;
  // The IDs of types of pointers to BufferBlock-decorated structs in Uniform
  // storage class, or Block-decorated structs in StorageBuffer storage class.
  // This is populated at the start of ValidateDecorations.
  std::unordered_set<uint32_t> pointer_to_storage_buffer_;
  // The IDs of struct types for storage buffers.
  // This is populated at the start of ValidateDecorations.
  std::unordered_set<uint32_t> struct_for_storage_buffer_;
  // The IDs of types of pointers to storage images.  This is populated in the
  // TypePass.
  std::unordered_set<uint32_t> pointer_to_storage_image_;
  // The IDs of types of pointers to tensors.  This is populated in the
  // TypePass.
  std::unordered_set<uint32_t> pointer_to_tensor_;

  /// Maps an id of DebugSource to a vector that contains the length of each
  /// line side of it. (Also will have the DebugSourceContinued source included)
  std::unordered_map<uint32_t, std::vector<uint32_t>> debug_source_line_length_;

  // Quick check if we have seen NonSemantic.Shader.DebugInfo.*
  // to know to try and print out a source line on an error message
  uint32_t shader_debug_info_set_id = 0;

  /// Maps ids to friendly names.
  std::unique_ptr<spvtools::FriendlyNameMapper> friendly_mapper_;
  spvtools::NameMapper name_mapper_;

  /// Variables used to reduce the number of diagnostic messages.
  uint32_t num_of_warnings_;
  uint32_t max_num_of_warnings_;

  struct DebugSourceInfo {
    uint32_t line_start;
    uint32_t line_end;
    uint32_t column_start;
    uint32_t column_end;
  };
  DebugSourceInfo GetDebugSourceInfo(const Instruction& inst);
  void InspectDebugLine(std::ostringstream& ss, const Instruction& inst);
  void InspectDebugGlobalVariable(std::ostringstream& ss,
                                  const Instruction& inst);
  void InspectDebugLocalVariable(std::ostringstream& ss, const Function& func,
                                 const Instruction& inst);
  void InspectFunctionCall(std::ostringstream& ss,
                           const Instruction& function_call_inst);
  void InspectLineAndFunctionDefinition(std::ostringstream& ss,
                                        const Function& func,
                                        const Instruction& inst);
  void InspectEntryPoint(std::ostringstream& ss, const Instruction& inst);
  void InspectDebugFunctionDefinition(std::ostringstream& ss,
                                      const Instruction& function_inst);
  void PrintShaderDebugInfoSource(std::ostringstream& ss,
                                  const Instruction& debug_source,
                                  const DebugSourceInfo& source_info);
};

}  // namespace val
}  // namespace spvtools

#endif  // SOURCE_VAL_VALIDATION_STATE_H_
