// Copyright (c) 2016 Google Inc.
// Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. 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 "source/opt/types.h"

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <sstream>
#include <string>
#include <unordered_set>

#include "source/util/hash_combine.h"
#include "source/util/make_unique.h"

namespace spvtools {
namespace opt {
namespace analysis {

using spvtools::utils::hash_combine;
using U32VecVec = std::vector<std::vector<uint32_t>>;

namespace {

// Returns true if the two vector of vectors are identical.
bool CompareTwoVectors(const U32VecVec a, const U32VecVec b) {
  const auto size = a.size();
  if (size != b.size()) return false;

  if (size == 0) return true;
  if (size == 1) return a.front() == b.front();

  std::vector<const std::vector<uint32_t>*> a_ptrs, b_ptrs;
  a_ptrs.reserve(size);
  a_ptrs.reserve(size);
  for (uint32_t i = 0; i < size; ++i) {
    a_ptrs.push_back(&a[i]);
    b_ptrs.push_back(&b[i]);
  }

  const auto cmp = [](const std::vector<uint32_t>* m,
                      const std::vector<uint32_t>* n) {
    return m->front() < n->front();
  };

  std::sort(a_ptrs.begin(), a_ptrs.end(), cmp);
  std::sort(b_ptrs.begin(), b_ptrs.end(), cmp);

  for (uint32_t i = 0; i < size; ++i) {
    if (*a_ptrs[i] != *b_ptrs[i]) return false;
  }
  return true;
}

}  // namespace

std::string Type::GetDecorationStr() const {
  std::ostringstream oss;
  oss << "[[";
  for (const auto& decoration : decorations_) {
    oss << "(";
    for (size_t i = 0; i < decoration.size(); ++i) {
      oss << (i > 0 ? ", " : "");
      oss << decoration.at(i);
    }
    oss << ")";
  }
  oss << "]]";
  return oss.str();
}

bool Type::HasSameDecorations(const Type* that) const {
  return CompareTwoVectors(decorations_, that->decorations_);
}

bool Type::IsUniqueType() const {
  switch (kind_) {
    case kPointer:
    case kStruct:
    case kArray:
    case kRuntimeArray:
    case kNodePayloadArrayAMDX:
      return false;
    default:
      return true;
  }
}

std::unique_ptr<Type> Type::Clone() const {
  std::unique_ptr<Type> type;
  switch (kind_) {
#define DeclareKindCase(kind)                   \
  case k##kind:                                 \
    type = MakeUnique<kind>(*this->As##kind()); \
    break
    DeclareKindCase(Void);
    DeclareKindCase(Bool);
    DeclareKindCase(Integer);
    DeclareKindCase(Float);
    DeclareKindCase(Vector);
    DeclareKindCase(Matrix);
    DeclareKindCase(Image);
    DeclareKindCase(Sampler);
    DeclareKindCase(SampledImage);
    DeclareKindCase(Array);
    DeclareKindCase(RuntimeArray);
    DeclareKindCase(Struct);
    DeclareKindCase(Opaque);
    DeclareKindCase(Pointer);
    DeclareKindCase(Function);
    DeclareKindCase(Event);
    DeclareKindCase(DeviceEvent);
    DeclareKindCase(ReserveId);
    DeclareKindCase(Queue);
    DeclareKindCase(Pipe);
    DeclareKindCase(ForwardPointer);
    DeclareKindCase(PipeStorage);
    DeclareKindCase(NamedBarrier);
    DeclareKindCase(AccelerationStructureNV);
    DeclareKindCase(CooperativeMatrixNV);
    DeclareKindCase(CooperativeMatrixKHR);
    DeclareKindCase(CooperativeVectorNV);
    DeclareKindCase(RayQueryKHR);
    DeclareKindCase(HitObjectNV);
    DeclareKindCase(HitObjectEXT);
    DeclareKindCase(TensorARM);
    DeclareKindCase(GraphARM);
    DeclareKindCase(BufferEXT);
#undef DeclareKindCase
    default:
      assert(false && "Unhandled type");
  }
  return type;
}

std::unique_ptr<Type> Type::RemoveDecorations() const {
  std::unique_ptr<Type> type(Clone());
  type->ClearDecorations();
  return type;
}

bool Type::operator==(const Type& other) const {
  if (kind_ != other.kind_) return false;

  switch (kind_) {
#define DeclareKindCase(kind) \
  case k##kind:               \
    return As##kind()->IsSame(&other)
    DeclareKindCase(Void);
    DeclareKindCase(Bool);
    DeclareKindCase(Integer);
    DeclareKindCase(Float);
    DeclareKindCase(Vector);
    DeclareKindCase(Matrix);
    DeclareKindCase(Image);
    DeclareKindCase(Sampler);
    DeclareKindCase(SampledImage);
    DeclareKindCase(Array);
    DeclareKindCase(RuntimeArray);
    DeclareKindCase(NodePayloadArrayAMDX);
    DeclareKindCase(Struct);
    DeclareKindCase(Opaque);
    DeclareKindCase(Pointer);
    DeclareKindCase(Function);
    DeclareKindCase(Event);
    DeclareKindCase(DeviceEvent);
    DeclareKindCase(ReserveId);
    DeclareKindCase(Queue);
    DeclareKindCase(Pipe);
    DeclareKindCase(ForwardPointer);
    DeclareKindCase(PipeStorage);
    DeclareKindCase(NamedBarrier);
    DeclareKindCase(AccelerationStructureNV);
    DeclareKindCase(CooperativeMatrixNV);
    DeclareKindCase(CooperativeMatrixKHR);
    DeclareKindCase(CooperativeVectorNV);
    DeclareKindCase(RayQueryKHR);
    DeclareKindCase(HitObjectNV);
    DeclareKindCase(HitObjectEXT);
    DeclareKindCase(TensorLayoutNV);
    DeclareKindCase(TensorViewNV);
    DeclareKindCase(TensorARM);
    DeclareKindCase(GraphARM);
    DeclareKindCase(BufferEXT);
#undef DeclareKindCase
    default:
      assert(false && "Unhandled type");
      return false;
  }
}

size_t Type::ComputeHashValue(size_t hash, SeenTypes* seen) const {
  // Linear search through a dense, cache coherent vector is faster than O(log
  // n) search in a complex data structure (eg std::set) for the generally small
  // number of nodes.  It also skips the overhead of an new/delete per Type
  // (when inserting/removing from a set).
  if (std::find(seen->begin(), seen->end(), this) != seen->end()) {
    return hash;
  }

  seen->push_back(this);

  hash = hash_combine(hash, uint32_t(kind_));
  for (const auto& d : decorations_) {
    hash = hash_combine(hash, d);
  }

  switch (kind_) {
#define DeclareKindCase(type)                             \
  case k##type:                                           \
    hash = As##type()->ComputeExtraStateHash(hash, seen); \
    break
    DeclareKindCase(Void);
    DeclareKindCase(Bool);
    DeclareKindCase(Integer);
    DeclareKindCase(Float);
    DeclareKindCase(Vector);
    DeclareKindCase(Matrix);
    DeclareKindCase(Image);
    DeclareKindCase(Sampler);
    DeclareKindCase(SampledImage);
    DeclareKindCase(Array);
    DeclareKindCase(RuntimeArray);
    DeclareKindCase(NodePayloadArrayAMDX);
    DeclareKindCase(Struct);
    DeclareKindCase(Opaque);
    DeclareKindCase(Pointer);
    DeclareKindCase(Function);
    DeclareKindCase(Event);
    DeclareKindCase(DeviceEvent);
    DeclareKindCase(ReserveId);
    DeclareKindCase(Queue);
    DeclareKindCase(Pipe);
    DeclareKindCase(ForwardPointer);
    DeclareKindCase(PipeStorage);
    DeclareKindCase(NamedBarrier);
    DeclareKindCase(AccelerationStructureNV);
    DeclareKindCase(CooperativeMatrixNV);
    DeclareKindCase(CooperativeMatrixKHR);
    DeclareKindCase(CooperativeVectorNV);
    DeclareKindCase(RayQueryKHR);
    DeclareKindCase(HitObjectNV);
    DeclareKindCase(HitObjectEXT);
    DeclareKindCase(TensorLayoutNV);
    DeclareKindCase(TensorViewNV);
    DeclareKindCase(TensorARM);
    DeclareKindCase(GraphARM);
    DeclareKindCase(BufferEXT);
#undef DeclareKindCase
    default:
      assert(false && "Unhandled type");
      break;
  }

  seen->pop_back();
  return hash;
}

size_t Type::HashValue() const {
  SeenTypes seen;
  return ComputeHashValue(0, &seen);
}

uint64_t Type::NumberOfComponents() const {
  switch (kind()) {
    case kVector:
      return AsVector()->element_count();
    case kMatrix:
      return AsMatrix()->element_count();
    case kArray: {
      Array::LengthInfo length_info = AsArray()->length_info();
      if (length_info.words[0] != Array::LengthInfo::kConstant) {
        return UINT64_MAX;
      }
      assert(length_info.words.size() <= 3 &&
             "The size of the array could not fit size_t.");
      uint64_t length = 0;
      length |= length_info.words[1];
      if (length_info.words.size() > 2) {
        length |= static_cast<uint64_t>(length_info.words[2]) << 32;
      }
      return length;
    }
    case kRuntimeArray:
      return UINT64_MAX;
    case kStruct:
      return AsStruct()->element_types().size();
    default:
      return 0;
  }
}

std::optional<uint32_t> Type::GetByteOffset(
    const std::vector<uint32_t>& access_chain) const {
  uint32_t offset = 0;
  const Type* current_type = this;
  for (uint32_t index : access_chain) {
    if (const Struct* struct_type = current_type->AsStruct()) {
      std::optional<uint32_t> member_offset;
      for (const auto& deco : struct_type->element_decorations()) {
        if (deco.first != index) continue;
        for (const auto& inst : deco.second) {
          if (inst[0] == uint32_t(spv::Decoration::Offset)) {
            member_offset = inst[1];
            break;
          }
        }
      }
      if (!member_offset) return {};
      offset += *member_offset;
      current_type = struct_type->element_types()[index];
    } else if (const Array* array_type = current_type->AsArray()) {
      std::optional<uint32_t> array_stride;
      for (const auto& deco : array_type->decorations()) {
        if (deco[0] == uint32_t(spv::Decoration::ArrayStride)) {
          array_stride = deco[1];
          break;
        }
      }
      if (!array_stride) return {};
      offset += *array_stride * index;
      current_type = array_type->element_type();
    } else if (const RuntimeArray* runtime_array_type =
                   current_type->AsRuntimeArray()) {
      std::optional<uint32_t> array_stride;
      for (const auto& deco : runtime_array_type->decorations()) {
        if (deco[0] == uint32_t(spv::Decoration::ArrayStride)) {
          array_stride = deco[1];
          break;
        }
      }
      if (!array_stride) return {};
      offset += *array_stride * index;
      current_type = runtime_array_type->element_type();
    } else if (const Matrix* matrix_type = current_type->AsMatrix()) {
      std::optional<uint32_t> matrix_stride;
      for (const auto& deco : matrix_type->decorations()) {
        if (deco[0] == uint32_t(spv::Decoration::MatrixStride)) {
          matrix_stride = deco[1];
          break;
        }
      }
      if (!matrix_stride) return {};
      offset += *matrix_stride * index;
      current_type = matrix_type->element_type();
    } else if (const Vector* vector_type = current_type->AsVector()) {
      const Type* component_type = vector_type->element_type();
      uint32_t component_size = 0;
      if (component_type->AsInteger()) {
        component_size = component_type->AsInteger()->width() / 8;
      } else if (component_type->AsFloat()) {
        component_size = component_type->AsFloat()->width() / 8;
      } else {
        return {};
      }
      offset += component_size * index;
      current_type = component_type;
    } else {
      return {};
    }
  }
  return offset;
}

bool Integer::IsSameImpl(const Type* that, IsSameCache*) const {
  const Integer* it = that->AsInteger();
  return it && width_ == it->width_ && signed_ == it->signed_ &&
         HasSameDecorations(that);
}

std::string Integer::str() const {
  std::ostringstream oss;
  oss << (signed_ ? "s" : "u") << "int" << width_;
  return oss.str();
}

size_t Integer::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  return hash_combine(hash, width_, signed_);
}

bool Float::IsSameImpl(const Type* that, IsSameCache*) const {
  const Float* ft = that->AsFloat();
  return ft && width_ == ft->width_ && encoding_ == ft->encoding_ &&
         HasSameDecorations(that);
}

std::string Float::str() const {
  std::ostringstream oss;
  switch (encoding_) {
    case spv::FPEncoding::BFloat16KHR:
      assert(width_ == 16);
      oss << "bfloat16";
      break;
    case spv::FPEncoding::Float8E4M3EXT:
      assert(width_ == 8);
      oss << "fp8e4m3";
      break;
    case spv::FPEncoding::Float8E5M2EXT:
      assert(width_ == 8);
      oss << "fp8e5m2";
      break;
    default:
      oss << "float" << width_;
      break;
  }
  return oss.str();
}

size_t Float::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  return hash_combine(hash, width_, encoding_);
}

Vector::Vector(const Type* type, uint32_t count)
    : Type(kVector), element_type_(type), count_(count) {
  assert(type->AsBool() || type->AsInteger() || type->AsFloat());
}

bool Vector::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Vector* vt = that->AsVector();
  if (!vt) return false;
  return count_ == vt->count_ &&
         element_type_->IsSameImpl(vt->element_type_, seen) &&
         HasSameDecorations(that);
}

std::string Vector::str() const {
  std::ostringstream oss;
  oss << "<" << element_type_->str() << ", " << count_ << ">";
  return oss.str();
}

size_t Vector::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  // prefer form that doesn't require push/pop from stack: add state and
  // make tail call.
  hash = hash_combine(hash, count_);
  return element_type_->ComputeHashValue(hash, seen);
}

Matrix::Matrix(const Type* type, uint32_t count)
    : Type(kMatrix), element_type_(type), count_(count) {
  assert(type->AsVector());
}

bool Matrix::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Matrix* mt = that->AsMatrix();
  if (!mt) return false;
  return count_ == mt->count_ &&
         element_type_->IsSameImpl(mt->element_type_, seen) &&
         HasSameDecorations(that);
}

std::string Matrix::str() const {
  std::ostringstream oss;
  oss << "<" << element_type_->str() << ", " << count_ << ">";
  return oss.str();
}

size_t Matrix::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  hash = hash_combine(hash, count_);
  return element_type_->ComputeHashValue(hash, seen);
}

Image::Image(Type* type, spv::Dim dimen, uint32_t d, bool array,
             bool multisample, uint32_t sampling, spv::ImageFormat f,
             spv::AccessQualifier qualifier)
    : Type(kImage),
      sampled_type_(type),
      dim_(dimen),
      depth_(d),
      arrayed_(array),
      ms_(multisample),
      sampled_(sampling),
      format_(f),
      access_qualifier_(qualifier) {
  // TODO(antiagainst): check sampled_type
}

bool Image::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Image* it = that->AsImage();
  if (!it) return false;
  return dim_ == it->dim_ && depth_ == it->depth_ && arrayed_ == it->arrayed_ &&
         ms_ == it->ms_ && sampled_ == it->sampled_ && format_ == it->format_ &&
         access_qualifier_ == it->access_qualifier_ &&
         sampled_type_->IsSameImpl(it->sampled_type_, seen) &&
         HasSameDecorations(that);
}

std::string Image::str() const {
  std::ostringstream oss;
  oss << "image(" << sampled_type_->str() << ", " << uint32_t(dim_) << ", "
      << depth_ << ", " << arrayed_ << ", " << ms_ << ", " << sampled_ << ", "
      << uint32_t(format_) << ", " << uint32_t(access_qualifier_) << ")";
  return oss.str();
}

size_t Image::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  hash = hash_combine(hash, uint32_t(dim_), depth_, arrayed_, ms_, sampled_,
                      uint32_t(format_), uint32_t(access_qualifier_));
  return sampled_type_->ComputeHashValue(hash, seen);
}

bool SampledImage::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const SampledImage* sit = that->AsSampledImage();
  if (!sit) return false;
  return image_type_->IsSameImpl(sit->image_type_, seen) &&
         HasSameDecorations(that);
}

std::string SampledImage::str() const {
  std::ostringstream oss;
  oss << "sampled_image(" << image_type_->str() << ")";
  return oss.str();
}

size_t SampledImage::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  return image_type_->ComputeHashValue(hash, seen);
}

Array::Array(const Type* type, const Array::LengthInfo& length_info_arg)
    : Type(kArray), element_type_(type), length_info_(length_info_arg) {
  assert(type != nullptr);
  assert(!type->AsVoid());
  // We always have a word to say which case we're in, followed
  // by at least one more word.
  assert(length_info_arg.words.size() >= 2);
}

bool Array::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Array* at = that->AsArray();
  if (!at) return false;
  bool is_same = element_type_->IsSameImpl(at->element_type_, seen);
  is_same = is_same && HasSameDecorations(that);
  is_same = is_same && (length_info_.words == at->length_info_.words);
  return is_same;
}

std::string Array::str() const {
  std::ostringstream oss;
  oss << "[" << element_type_->str() << ", id(" << LengthId() << "), words(";
  const char* spacer = "";
  for (auto w : length_info_.words) {
    oss << spacer << w;
    spacer = ",";
  }
  oss << ")]";
  return oss.str();
}

size_t Array::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  hash = hash_combine(hash, length_info_.words);
  return element_type_->ComputeHashValue(hash, seen);
}

void Array::ReplaceElementType(const Type* type) { element_type_ = type; }

Array::LengthInfo Array::GetConstantLengthInfo(uint32_t const_id,
                                               uint32_t length) const {
  std::vector<uint32_t> extra_words{LengthInfo::Case::kConstant, length};
  return {const_id, extra_words};
}

RuntimeArray::RuntimeArray(const Type* type)
    : Type(kRuntimeArray), element_type_(type) {
  assert(!type->AsVoid());
}

bool RuntimeArray::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const RuntimeArray* rat = that->AsRuntimeArray();
  if (!rat) return false;
  return element_type_->IsSameImpl(rat->element_type_, seen) &&
         HasSameDecorations(that);
}

std::string RuntimeArray::str() const {
  std::ostringstream oss;
  oss << "[" << element_type_->str() << "]";
  return oss.str();
}

size_t RuntimeArray::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  return element_type_->ComputeHashValue(hash, seen);
}

void RuntimeArray::ReplaceElementType(const Type* type) {
  element_type_ = type;
}

NodePayloadArrayAMDX::NodePayloadArrayAMDX(const Type* type)
    : Type(kNodePayloadArrayAMDX), element_type_(type) {
  assert(!type->AsVoid());
}

bool NodePayloadArrayAMDX::IsSameImpl(const Type* that,
                                      IsSameCache* seen) const {
  const NodePayloadArrayAMDX* rat = that->AsNodePayloadArrayAMDX();
  if (!rat) return false;
  return element_type_->IsSameImpl(rat->element_type_, seen) &&
         HasSameDecorations(that);
}

std::string NodePayloadArrayAMDX::str() const {
  std::ostringstream oss;
  oss << "[" << element_type_->str() << "]";
  return oss.str();
}

size_t NodePayloadArrayAMDX::ComputeExtraStateHash(size_t hash,
                                                   SeenTypes* seen) const {
  return element_type_->ComputeHashValue(hash, seen);
}

void NodePayloadArrayAMDX::ReplaceElementType(const Type* type) {
  element_type_ = type;
}

Struct::Struct(const std::vector<const Type*>& types)
    : Type(kStruct), element_types_(types) {
  for (const auto* t : types) {
    (void)t;
    assert(!t->AsVoid());
  }
}

void Struct::AddMemberDecoration(uint32_t index,
                                 std::vector<uint32_t>&& decoration) {
  if (index >= element_types_.size()) {
    assert(0 && "index out of bound");
    return;
  }

  element_decorations_[index].push_back(std::move(decoration));
}

bool Struct::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Struct* st = that->AsStruct();
  if (!st) return false;
  if (element_types_.size() != st->element_types_.size()) return false;
  const auto size = element_decorations_.size();
  if (size != st->element_decorations_.size()) return false;
  if (!HasSameDecorations(that)) return false;

  for (size_t i = 0; i < element_types_.size(); ++i) {
    if (!element_types_[i]->IsSameImpl(st->element_types_[i], seen))
      return false;
  }
  for (const auto& p : element_decorations_) {
    if (st->element_decorations_.count(p.first) == 0) return false;
    if (!CompareTwoVectors(p.second, st->element_decorations_.at(p.first)))
      return false;
  }
  return true;
}

std::string Struct::str() const {
  std::ostringstream oss;
  oss << "{";
  const size_t count = element_types_.size();
  for (size_t i = 0; i < count; ++i) {
    oss << element_types_[i]->str();
    if (i + 1 != count) oss << ", ";
  }
  oss << "}";
  return oss.str();
}

size_t Struct::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  for (auto* t : element_types_) {
    hash = t->ComputeHashValue(hash, seen);
  }
  for (const auto& pair : element_decorations_) {
    hash = hash_combine(hash, pair.first, pair.second);
  }
  return hash;
}

bool Opaque::IsSameImpl(const Type* that, IsSameCache*) const {
  const Opaque* ot = that->AsOpaque();
  if (!ot) return false;
  return name_ == ot->name_ && HasSameDecorations(that);
}

std::string Opaque::str() const {
  std::ostringstream oss;
  oss << "opaque('" << name_ << "')";
  return oss.str();
}

size_t Opaque::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  return hash_combine(hash, name_);
}

Pointer::Pointer(const Type* type, spv::StorageClass sc)
    : Type(kPointer), pointee_type_(type), storage_class_(sc) {}

bool Pointer::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Pointer* pt = that->AsPointer();
  if (!pt) return false;
  if (storage_class_ != pt->storage_class_) return false;
  auto p = seen->insert(std::make_pair(this, that->AsPointer()));
  if (!p.second) {
    return true;
  }
  if (pointee_type_ != nullptr && pt->pointee_type_ != nullptr) {
    bool same_pointee = pointee_type_->IsSameImpl(pt->pointee_type_, seen);
    seen->erase(p.first);
    if (!same_pointee) {
      return false;
    }
  } else {
    seen->erase(p.first);
    // Either both are untyped or it is mixed typed and untyped.
    if (pointee_type_ != pt->pointee_type_) {
      return false;
    }
  }
  return HasSameDecorations(that);
}

std::string Pointer::str() const {
  std::ostringstream os;
  if (pointee_type_) {
    os << pointee_type_->str();
  } else {
    os << "untyped_ptr";
  }
  os << " " << static_cast<uint32_t>(storage_class_) << "*";
  return os.str();
}

size_t Pointer::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  hash = hash_combine(hash, uint32_t(storage_class_));
  if (pointee_type_) {
    hash = pointee_type_->ComputeHashValue(hash, seen);
  }
  return hash;
}

void Pointer::SetPointeeType(const Type* type) { pointee_type_ = type; }

Function::Function(const Type* ret_type, const std::vector<const Type*>& params)
    : Type(kFunction), return_type_(ret_type), param_types_(params) {}

Function::Function(const Type* ret_type, std::vector<const Type*>& params)
    : Type(kFunction), return_type_(ret_type), param_types_(params) {}

bool Function::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const Function* ft = that->AsFunction();
  if (!ft) return false;
  if (!return_type_->IsSameImpl(ft->return_type_, seen)) return false;
  if (param_types_.size() != ft->param_types_.size()) return false;
  for (size_t i = 0; i < param_types_.size(); ++i) {
    if (!param_types_[i]->IsSameImpl(ft->param_types_[i], seen)) return false;
  }
  return HasSameDecorations(that);
}

std::string Function::str() const {
  std::ostringstream oss;
  const size_t count = param_types_.size();
  oss << "(";
  for (size_t i = 0; i < count; ++i) {
    oss << param_types_[i]->str();
    if (i + 1 != count) oss << ", ";
  }
  oss << ") -> " << return_type_->str();
  return oss.str();
}

size_t Function::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  for (const auto* t : param_types_) {
    hash = t->ComputeHashValue(hash, seen);
  }
  return return_type_->ComputeHashValue(hash, seen);
}

void Function::SetReturnType(const Type* type) { return_type_ = type; }

bool Pipe::IsSameImpl(const Type* that, IsSameCache*) const {
  const Pipe* pt = that->AsPipe();
  if (!pt) return false;
  return access_qualifier_ == pt->access_qualifier_ && HasSameDecorations(that);
}

std::string Pipe::str() const {
  std::ostringstream oss;
  oss << "pipe(" << uint32_t(access_qualifier_) << ")";
  return oss.str();
}

size_t Pipe::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  return hash_combine(hash, uint32_t(access_qualifier_));
}

bool ForwardPointer::IsSameImpl(const Type* that, IsSameCache*) const {
  const ForwardPointer* fpt = that->AsForwardPointer();
  if (!fpt) return false;
  return (pointer_ && fpt->pointer_ ? *pointer_ == *fpt->pointer_
                                    : target_id_ == fpt->target_id_) &&
         storage_class_ == fpt->storage_class_ && HasSameDecorations(that);
}

std::string ForwardPointer::str() const {
  std::ostringstream oss;
  oss << "forward_pointer(";
  if (pointer_ != nullptr) {
    oss << pointer_->str();
  } else {
    oss << target_id_;
  }
  oss << ")";
  return oss.str();
}

size_t ForwardPointer::ComputeExtraStateHash(size_t hash,
                                             SeenTypes* seen) const {
  hash = hash_combine(hash, target_id_, uint32_t(storage_class_));
  if (pointer_) hash = pointer_->ComputeHashValue(hash, seen);
  return hash;
}

CooperativeMatrixNV::CooperativeMatrixNV(const Type* type, const uint32_t scope,
                                         const uint32_t rows,
                                         const uint32_t columns)
    : Type(kCooperativeMatrixNV),
      component_type_(type),
      scope_id_(scope),
      rows_id_(rows),
      columns_id_(columns) {
  assert(type != nullptr);
  assert(scope != 0);
  assert(rows != 0);
  assert(columns != 0);
}

std::string CooperativeMatrixNV::str() const {
  std::ostringstream oss;
  oss << "<" << component_type_->str() << ", " << scope_id_ << ", " << rows_id_
      << ", " << columns_id_ << ">";
  return oss.str();
}

size_t CooperativeMatrixNV::ComputeExtraStateHash(size_t hash,
                                                  SeenTypes* seen) const {
  hash = hash_combine(hash, scope_id_, rows_id_, columns_id_);
  return component_type_->ComputeHashValue(hash, seen);
}

bool CooperativeMatrixNV::IsSameImpl(const Type* that,
                                     IsSameCache* seen) const {
  const CooperativeMatrixNV* mt = that->AsCooperativeMatrixNV();
  if (!mt) return false;
  return component_type_->IsSameImpl(mt->component_type_, seen) &&
         scope_id_ == mt->scope_id_ && rows_id_ == mt->rows_id_ &&
         columns_id_ == mt->columns_id_ && HasSameDecorations(that);
}

CooperativeMatrixKHR::CooperativeMatrixKHR(const Type* type,
                                           const uint32_t scope,
                                           const uint32_t rows,
                                           const uint32_t columns,
                                           const uint32_t use)
    : Type(kCooperativeMatrixKHR),
      component_type_(type),
      scope_id_(scope),
      rows_id_(rows),
      columns_id_(columns),
      use_id_(use) {
  assert(type != nullptr);
  assert(scope != 0);
  assert(rows != 0);
  assert(columns != 0);
}

std::string CooperativeMatrixKHR::str() const {
  std::ostringstream oss;
  oss << "<" << component_type_->str() << ", " << scope_id_ << ", " << rows_id_
      << ", " << columns_id_ << ", " << use_id_ << ">";
  return oss.str();
}

size_t CooperativeMatrixKHR::ComputeExtraStateHash(size_t hash,
                                                   SeenTypes* seen) const {
  hash = hash_combine(hash, scope_id_, rows_id_, columns_id_, use_id_);
  return component_type_->ComputeHashValue(hash, seen);
}

bool CooperativeMatrixKHR::IsSameImpl(const Type* that,
                                      IsSameCache* seen) const {
  const CooperativeMatrixKHR* mt = that->AsCooperativeMatrixKHR();
  if (!mt) return false;
  return component_type_->IsSameImpl(mt->component_type_, seen) &&
         scope_id_ == mt->scope_id_ && rows_id_ == mt->rows_id_ &&
         columns_id_ == mt->columns_id_ && use_id_ == mt->use_id_ &&
         HasSameDecorations(that);
}

TensorLayoutNV::TensorLayoutNV(const uint32_t dim, const uint32_t clamp_mode)
    : Type(kTensorLayoutNV), dim_id_(dim), clamp_mode_id_(clamp_mode) {}

std::string TensorLayoutNV::str() const {
  std::ostringstream oss;
  oss << "<" << dim_id_ << ", " << clamp_mode_id_ << ">";
  return oss.str();
}

size_t TensorLayoutNV::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  return hash_combine(hash, dim_id_, clamp_mode_id_);
}

bool TensorLayoutNV::IsSameImpl(const Type* that, IsSameCache*) const {
  const TensorLayoutNV* tl = that->AsTensorLayoutNV();
  if (!tl) return false;
  return dim_id_ == tl->dim_id_ && clamp_mode_id_ == tl->clamp_mode_id_;
}

TensorViewNV::TensorViewNV(const uint32_t dim, const uint32_t clamp_mode,
                           const std::vector<uint32_t>& perm)
    : Type(kTensorViewNV),
      dim_id_(dim),
      has_dimensions_id_(clamp_mode),
      perm_(perm) {}

std::string TensorViewNV::str() const {
  std::ostringstream oss;
  oss << "<" << dim_id_ << ", " << has_dimensions_id_;
  for (auto p : perm_) {
    oss << ", " << p;
  }
  oss << ">";
  return oss.str();
}

size_t TensorViewNV::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  return hash_combine(hash, dim_id_, has_dimensions_id_, perm_);
}

bool TensorViewNV::IsSameImpl(const Type* that, IsSameCache*) const {
  const TensorViewNV* tv = that->AsTensorViewNV();
  if (!tv) return false;
  return dim_id_ == tv->dim_id_ &&
         has_dimensions_id_ == tv->has_dimensions_id_ && perm_ == tv->perm_;
}

CooperativeVectorNV::CooperativeVectorNV(const Type* type,
                                         const uint32_t components)
    : Type(kCooperativeVectorNV),
      component_type_(type),
      components_(components) {
  assert(type != nullptr);
  assert(components != 0);
}

std::string CooperativeVectorNV::str() const {
  std::ostringstream oss;
  oss << "<" << component_type_->str() << ", " << components_ << ">";
  return oss.str();
}

size_t CooperativeVectorNV::ComputeExtraStateHash(size_t hash,
                                                  SeenTypes* seen) const {
  hash = hash_combine(hash, components_);
  return component_type_->ComputeHashValue(hash, seen);
}

bool CooperativeVectorNV::IsSameImpl(const Type* that,
                                     IsSameCache* seen) const {
  const CooperativeVectorNV* mt = that->AsCooperativeVectorNV();
  if (!mt) return false;
  return component_type_->IsSameImpl(mt->component_type_, seen) &&
         components_ == mt->components_ && HasSameDecorations(that);
}

TensorARM::TensorARM(const Type* elty, const uint32_t rank,
                     const uint32_t shape)
    : Type(kTensorARM), element_type_(elty), rank_id_(rank), shape_id_(shape) {
  assert(elty != nullptr);
  if (shape != 0) {
    assert(rank != 0);
  }
}

std::string TensorARM::str() const {
  std::ostringstream oss;
  oss << "tensor<" << element_type_->str() << ", id(" << rank_id_ << "), id("
      << shape_id_ << ")>";
  return oss.str();
}

size_t TensorARM::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  hash = hash_combine(hash, rank_id_);
  hash = hash_combine(hash, shape_id_);
  return element_type_->ComputeHashValue(hash, seen);
}

bool TensorARM::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const TensorARM* tt = that->AsTensorARM();
  if (!tt) return false;
  return element_type_->IsSameImpl(tt->element_type_, seen) &&
         rank_id_ == tt->rank_id_ && shape_id_ == tt->shape_id_ &&
         HasSameDecorations(that);
}

GraphARM::GraphARM(const uint32_t num_inputs,
                   const std::vector<const Type*>& io_types)
    : Type(kGraphARM), num_inputs_(num_inputs), io_types_(io_types) {
  assert(io_types.size() > 0);
}

std::string GraphARM::str() const {
  std::ostringstream oss;
  oss << "graph<" << num_inputs_;
  for (auto ioty : io_types_) {
    oss << "," << ioty->str();
  }
  oss << ">";
  return oss.str();
}

bool GraphARM::is_shaped() const {
  // A graph is considered to be shaped if all its interface tensors are shaped
  for (auto ioty : io_types_) {
    auto tensor_type = ioty->AsTensorARM();
    assert(tensor_type);
    if (!tensor_type->is_shaped()) {
      return false;
    }
  }
  return true;
}

size_t GraphARM::ComputeExtraStateHash(size_t hash, SeenTypes* seen) const {
  hash = hash_combine(hash, num_inputs_);
  for (auto ioty : io_types_) {
    hash = ioty->ComputeHashValue(hash, seen);
  }
  return hash;
}

bool GraphARM::IsSameImpl(const Type* that, IsSameCache* seen) const {
  const GraphARM* og = that->AsGraphARM();
  if (!og) {
    return false;
  }
  if (num_inputs_ != og->num_inputs_) {
    return false;
  }
  if (io_types_.size() != og->io_types_.size()) {
    return false;
  }
  for (size_t i = 0; i < io_types_.size(); i++) {
    if (!io_types_[i]->IsSameImpl(og->io_types_[i], seen)) {
      return false;
    }
  }
  return true;
}

BufferEXT::BufferEXT(spv::StorageClass storage_class)
    : Type(kBufferEXT), storage_class_(storage_class) {}

std::string BufferEXT::str() const {
  std::ostringstream oss;
  oss << "buffer<" << static_cast<uint32_t>(storage_class_) << ">";
  return oss.str();
}

size_t BufferEXT::ComputeExtraStateHash(size_t hash, SeenTypes*) const {
  hash = hash_combine(hash, static_cast<uint32_t>(storage_class_));
  return hash;
}

bool BufferEXT::IsSameImpl(const Type* that, IsSameCache*) const {
  const BufferEXT* og = that->AsBufferEXT();
  if (!og) {
    return false;
  }
  if (storage_class_ != og->storage_class_) {
    return false;
  }
  return true;
}

}  // namespace analysis
}  // namespace opt
}  // namespace spvtools
