text: // Copyright (c) 2016 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/types.h"
  
  #include <memory>
  #include <utility>
  #include <vector>
  
  #include "gtest/gtest.h"
  #include "source/util/make_unique.h"
  
  namespace spvtools {
  namespace opt {
  namespace analysis {
  namespace {
  
  // Fixture class providing some element types.
  class SameTypeTest : public ::testing::Test {
   protected:
    void SetUp() override {
      void_t_ = MakeUnique<Void>();
      u32_t_ = MakeUnique<Integer>(32, false);
      f64_t_ = MakeUnique<Float>(64);
      v3u32_t_ = MakeUnique<Vector>(u32_t_.get(), 3);
      image_t_ =
          MakeUnique<Image>(f64_t_.get(), SpvDim2D, 1, 1, 0, 0, SpvImageFormatR16,
                            SpvAccessQualifierReadWrite);
    }
  
    // Element types to be used for constructing other types for testing.
    std::unique_ptr<Type> void_t_;
    std::unique_ptr<Type> u32_t_;
    std::unique_ptr<Type> f64_t_;
    std::unique_ptr<Type> v3u32_t_;
    std::unique_ptr<Type> image_t_;
  };
  
  #define TestMultipleInstancesOfTheSameTypeQualified(ty, name, ...)        \
    TEST_F(SameTypeTest, MultiSame##ty##name) {                             \
      std::vector<std::unique_ptr<Type>> types;                             \
      for (int i = 0; i < 10; ++i) types.emplace_back(new ty(__VA_ARGS__)); \
      for (size_t i = 0; i < types.size(); ++i) {                           \
        for (size_t j = 0; j < types.size(); ++j) {                         \
          EXPECT_TRUE(types[i]->IsSame(types[j].get()))                     \
              << "expected '" << types[i]->str() << "' is the same as '"    \
              << types[j]->str() << "'";                                    \
          EXPECT_TRUE(*types[i] == *types[j])                               \
              << "expected '" << types[i]->str() << "' is the same as '"    \
              << types[j]->str() << "'";                                    \
        }                                                                   \
      }                                                                     \
    }
  #define TestMultipleInstancesOfTheSameType(ty, ...) \
    TestMultipleInstancesOfTheSameTypeQualified(ty, Simple, __VA_ARGS__)
  
  // clang-format off
  TestMultipleInstancesOfTheSameType(Void)
  TestMultipleInstancesOfTheSameType(Bool)
  TestMultipleInstancesOfTheSameType(Integer, 32, true)
  TestMultipleInstancesOfTheSameType(Float, 64)
  TestMultipleInstancesOfTheSameType(Vector, u32_t_.get(), 3)
  TestMultipleInstancesOfTheSameType(Matrix, v3u32_t_.get(), 4)
  TestMultipleInstancesOfTheSameType(Image, f64_t_.get(), SpvDimCube, 0, 0, 1, 1,
                                     SpvImageFormatRgb10A2,
                                     SpvAccessQualifierWriteOnly)
  TestMultipleInstancesOfTheSameType(Sampler)
  TestMultipleInstancesOfTheSameType(SampledImage, image_t_.get())
  // There are three classes of arrays, based on the kinds of length information
  // they have.
  // 1. Array length is a constant or spec constant without spec ID, with literals
  // for the constant value.
  TestMultipleInstancesOfTheSameTypeQualified(Array, LenConstant, u32_t_.get(),
                                              Array::LengthInfo{42,
                                                                {
                                                                    0,
                                                                    9999,
                                                                }})
  // 2. Array length is a spec constant with a given spec id.
  TestMultipleInstancesOfTheSameTypeQualified(Array, LenSpecId, u32_t_.get(),
                                              Array::LengthInfo{42, {1, 99}})
  // 3. Array length is an OpSpecConstantOp expression
  TestMultipleInstancesOfTheSameTypeQualified(Array, LenDefiningId, u32_t_.get(),
                                              Array::LengthInfo{42, {2, 42}})
  
  TestMultipleInstancesOfTheSameType(RuntimeArray, u32_t_.get())
  TestMultipleInstancesOfTheSameType(Struct, std::vector<const Type*>{
                                                 u32_t_.get(), f64_t_.get()})
  TestMultipleInstancesOfTheSameType(Opaque, "testing rocks")
  TestMultipleInstancesOfTheSameType(Pointer, u32_t_.get(), SpvStorageClassInput)
  TestMultipleInstancesOfTheSameType(Function, u32_t_.get(),
                                     {f64_t_.get(), f64_t_.get()})
  TestMultipleInstancesOfTheSameType(Event)
  TestMultipleInstancesOfTheSameType(DeviceEvent)
  TestMultipleInstancesOfTheSameType(ReserveId)
  TestMultipleInstancesOfTheSameType(Queue)
  TestMultipleInstancesOfTheSameType(Pipe, SpvAccessQualifierReadWrite)
  TestMultipleInstancesOfTheSameType(ForwardPointer, 10, SpvStorageClassUniform)
  TestMultipleInstancesOfTheSameType(PipeStorage)
  TestMultipleInstancesOfTheSameType(NamedBarrier)
  TestMultipleInstancesOfTheSameType(AccelerationStructureNV)
  #undef TestMultipleInstanceOfTheSameType
  #undef TestMultipleInstanceOfTheSameTypeQual
  
  std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
    // clang-format on
    // Types in this test case are only equal to themselves, nothing else.
    std::vector<std::unique_ptr<Type>> types;
  
    // Forward Pointer
    types.emplace_back(new ForwardPointer(10000, SpvStorageClassInput));
    types.emplace_back(new ForwardPointer(20000, SpvStorageClassInput));
  
    // Void, Bool
    types.emplace_back(new Void());
    auto* voidt = types.back().get();
    types.emplace_back(new Bool());
    auto* boolt = types.back().get();
  
    // Integer
    types.emplace_back(new Integer(32, true));
    auto* s32 = types.back().get();
    types.emplace_back(new Integer(32, false));
    types.emplace_back(new Integer(64, true));
    types.emplace_back(new Integer(64, false));
    auto* u64 = types.back().get();
  
    // Float
    types.emplace_back(new Float(32));
    auto* f32 = types.back().get();
    types.emplace_back(new Float(64));
  
    // Vector
    types.emplace_back(new Vector(s32, 2));
    types.emplace_back(new Vector(s32, 3));
    auto* v3s32 = types.back().get();
    types.emplace_back(new Vector(u64, 4));
    types.emplace_back(new Vector(f32, 3));
    auto* v3f32 = types.back().get();
  
    // Matrix
    types.emplace_back(new Matrix(v3s32, 3));
    types.emplace_back(new Matrix(v3s32, 4));
    types.emplace_back(new Matrix(v3f32, 4));
  
    // Images
    types.emplace_back(new Image(s32, SpvDim2D, 0, 0, 0, 0, SpvImageFormatRg8,
                                 SpvAccessQualifierReadOnly));
    auto* image1 = types.back().get();
    types.emplace_back(new Image(s32, SpvDim2D, 0, 1, 0, 0, SpvImageFormatRg8,
                                 SpvAccessQualifierReadOnly));
    types.emplace_back(new Image(s32, SpvDim3D, 0, 1, 0, 0, SpvImageFormatRg8,
                                 SpvAccessQualifierReadOnly));
    types.emplace_back(new Image(voidt, SpvDim3D, 0, 1, 0, 1, SpvImageFormatRg8,
                                 SpvAccessQualifierReadWrite));
    auto* image2 = types.back().get();
  
    // Sampler
    types.emplace_back(new Sampler());
  
    // Sampled Image
    types.emplace_back(new SampledImage(image1));
    types.emplace_back(new SampledImage(image2));
  
    // Array
    // Length is constant with integer bit representation of 42.
    types.emplace_back(new Array(f32, Array::LengthInfo{99u, {0, 42u}}));
    auto* a42f32 = types.back().get();
    // Differs from previous in length value only.
    types.emplace_back(new Array(f32, Array::LengthInfo{99u, {0, 44u}}));
    // Length is 64-bit constant integer value 42.
    types.emplace_back(new Array(u64, Array::LengthInfo{100u, {0, 42u, 0u}}));
    // Differs from previous in length value only.
    types.emplace_back(new Array(u64, Array::LengthInfo{100u, {0, 44u, 0u}}));
  
    // Length is spec constant with spec id 18 and default value 44.
    types.emplace_back(new Array(f32, Array::LengthInfo{99u,
                                                        {
                                                            1,
                                                            18u,
                                                            44u,
                                                        }}));
    // Differs from previous in spec id only.
    types.emplace_back(new Array(f32, Array::LengthInfo{99u, {1, 19u, 44u}}));
    // Differs from previous in literal value only.
    types.emplace_back(new Array(f32, Array::LengthInfo{99u, {1, 19u, 48u}}));
    // Length is spec constant op with id 42.
    types.emplace_back(new Array(f32, Array::LengthInfo{42u, {2, 42}}));
    // Differs from previous in result id only.
    types.emplace_back(new Array(f32, Array::LengthInfo{43u, {2, 43}}));
  
    // RuntimeArray
    types.emplace_back(new RuntimeArray(v3f32));
    types.emplace_back(new RuntimeArray(v3s32));
    auto* rav3s32 = types.back().get();
  
    // Struct
    types.emplace_back(new Struct(std::vector<const Type*>{s32}));
    types.emplace_back(new Struct(std::vector<const Type*>{s32, f32}));
    auto* sts32f32 = types.back().get();
    types.emplace_back(
        new Struct(std::vector<const Type*>{u64, a42f32, rav3s32}));
  
    // Opaque
    types.emplace_back(new Opaque(""));
    types.emplace_back(new Opaque("hello"));
    types.emplace_back(new Opaque("world"));
  
    // Pointer
    types.emplace_back(new Pointer(f32, SpvStorageClassInput));
    types.emplace_back(new Pointer(sts32f32, SpvStorageClassFunction));
    types.emplace_back(new Pointer(a42f32, SpvStorageClassFunction));
    types.emplace_back(new Pointer(voidt, SpvStorageClassFunction));
  
    // Function
    types.emplace_back(new Function(voidt, {}));
    types.emplace_back(new Function(voidt, {boolt}));
    types.emplace_back(new Function(voidt, {boolt, s32}));
    types.emplace_back(new Function(s32, {boolt, s32}));
  
    // Event, Device Event, Reserve Id, Queue,
    types.emplace_back(new Event());
    types.emplace_back(new DeviceEvent());
    types.emplace_back(new ReserveId());
    types.emplace_back(new Queue());
  
    // Pipe, Forward Pointer, PipeStorage, NamedBarrier
    types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
    types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
    types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
    types.emplace_back(new ForwardPointer(2, SpvStorageClassInput));
    types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform));
    types.emplace_back(new PipeStorage());
    types.emplace_back(new NamedBarrier());
  
    return types;
  }
  
  TEST(Types, AllTypes) {
    // Types in this test case are only equal to themselves, nothing else.
    std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
  
    for (size_t i = 0; i < types.size(); ++i) {
      for (size_t j = 0; j < types.size(); ++j) {
        if (i == j) {
          EXPECT_TRUE(types[i]->IsSame(types[j].get()))
              << "expected '" << types[i]->str() << "' is the same as '"
              << types[j]->str() << "'";
        } else {
          EXPECT_FALSE(types[i]->IsSame(types[j].get()))
              << "entry (" << i << "," << j << ")  expected '" << types[i]->str()
              << "' is different to '" << types[j]->str() << "'";
        }
      }
    }
  }
  
  TEST(Types, IntSignedness) {
    std::vector<bool> signednesses = {true, false, false, true};
    std::vector<std::unique_ptr<Integer>> types;
    for (bool s : signednesses) {
      types.emplace_back(new Integer(32, s));
    }
    for (size_t i = 0; i < signednesses.size(); i++) {
      EXPECT_EQ(signednesses[i], types[i]->IsSigned());
    }
  }
  
  TEST(Types, IntWidth) {
    std::vector<uint32_t> widths = {1, 2, 4, 8, 16, 32, 48, 64, 128};
    std::vector<std::unique_ptr<Integer>> types;
    for (uint32_t w : widths) {
      types.emplace_back(new Integer(w, true));
    }
    for (size_t i = 0; i < widths.size(); i++) {
      EXPECT_EQ(widths[i], types[i]->width());
    }
  }
  
  TEST(Types, FloatWidth) {
    std::vector<uint32_t> widths = {1, 2, 4, 8, 16, 32, 48, 64, 128};
    std::vector<std::unique_ptr<Float>> types;
    for (uint32_t w : widths) {
      types.emplace_back(new Float(w));
    }
    for (size_t i = 0; i < widths.size(); i++) {
      EXPECT_EQ(widths[i], types[i]->width());
    }
  }
  
  TEST(Types, VectorElementCount) {
    auto s32 = MakeUnique<Integer>(32, true);
    for (uint32_t c : {2, 3, 4}) {
      auto s32v = MakeUnique<Vector>(s32.get(), c);
      EXPECT_EQ(c, s32v->element_count());
    }
  }
  
  TEST(Types, MatrixElementCount) {
    auto s32 = MakeUnique<Integer>(32, true);
    auto s32v4 = MakeUnique<Vector>(s32.get(), 4);
    for (uint32_t c : {1, 2, 3, 4, 10, 100}) {
      auto s32m = MakeUnique<Matrix>(s32v4.get(), c);
      EXPECT_EQ(c, s32m->element_count());
    }
  }
  
  TEST(Types, IsUniqueType) {
    std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
  
    for (auto& t : types) {
      bool expectation = true;
      // Disallowing variable pointers.
      switch (t->kind()) {
        case Type::kArray:
        case Type::kRuntimeArray:
        case Type::kStruct:
          expectation = false;
          break;
        default:
          break;
      }
      EXPECT_EQ(t->IsUniqueType(false), expectation)
          << "expected '" << t->str() << "' to be a "
          << (expectation ? "" : "non-") << "unique type";
  
      // Allowing variables pointers.
      if (t->AsPointer()) expectation = false;
      EXPECT_EQ(t->IsUniqueType(true), expectation)
          << "expected '" << t->str() << "' to be a "
          << (expectation ? "" : "non-") << "unique type";
    }
  }
  
  std::vector<std::unique_ptr<Type>> GenerateAllTypesWithDecorations() {
    std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
    uint32_t elems = 1;
    uint32_t decs = 1;
    for (auto& t : types) {
      for (uint32_t i = 0; i < (decs % 10); ++i) {
        std::vector<uint32_t> decoration;
        for (uint32_t j = 0; j < (elems % 4) + 1; ++j) {
          decoration.push_back(j);
        }
        t->AddDecoration(std::move(decoration));
        ++elems;
        ++decs;
      }
    }
  
    return types;
  }
  
  TEST(Types, Clone) {
    std::vector<std::unique_ptr<Type>> types = GenerateAllTypesWithDecorations();
    for (auto& t : types) {
      auto clone = t->Clone();
      EXPECT_TRUE(*t == *clone);
      EXPECT_TRUE(t->HasSameDecorations(clone.get()));
      EXPECT_NE(clone.get(), t.get());
    }
  }
  
  TEST(Types, RemoveDecorations) {
    std::vector<std::unique_ptr<Type>> types = GenerateAllTypesWithDecorations();
    for (auto& t : types) {
      auto decorationless = t->RemoveDecorations();
      EXPECT_EQ(*t == *decorationless, t->decoration_empty());
      EXPECT_EQ(t->HasSameDecorations(decorationless.get()),
                t->decoration_empty());
      EXPECT_NE(t.get(), decorationless.get());
    }
  }
  
  }  // namespace
  }  // namespace analysis
  }  // namespace opt
  }  // namespace spvtools
  