// Copyright (c) 2020 Google LLC
//
// 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/fuzz/transformation_compute_data_synonym_fact_closure.h"
#include "test/fuzz/fuzz_test_util.h"

namespace spvtools {
namespace fuzz {
namespace {

TEST(TransformationComputeDataSynonymFactClosureTest, DataSynonymFacts) {
  // The SPIR-V types and constants come from the following code.  The body of
  // the SPIR-V function then constructs a composite that is synonymous with
  // myT.
  //
  // #version 310 es
  //
  // precision highp float;
  //
  // struct S {
  //   int a;
  //   uvec2 b;
  // };
  //
  // struct T {
  //   bool c[5];
  //   mat4x2 d;
  //   S e;
  // };
  //
  // void main() {
  //   T myT = T(bool[5](true, false, true, false, true),
  //             mat4x2(vec2(1.0, 2.0), vec2(3.0, 4.0),
  // 	           vec2(5.0, 6.0), vec2(7.0, 8.0)),
  //             S(10, uvec2(100u, 200u)));
  // }

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %15 "S"
               OpMemberName %15 0 "a"
               OpMemberName %15 1 "b"
               OpName %16 "T"
               OpMemberName %16 0 "c"
               OpMemberName %16 1 "d"
               OpMemberName %16 2 "e"
               OpName %18 "myT"
               OpMemberDecorate %15 0 RelaxedPrecision
               OpMemberDecorate %15 1 RelaxedPrecision
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeBool
          %7 = OpTypeInt 32 0
          %8 = OpConstant %7 5
          %9 = OpTypeArray %6 %8
         %10 = OpTypeFloat 32
         %11 = OpTypeVector %10 2
         %12 = OpTypeMatrix %11 4
         %13 = OpTypeInt 32 1
         %14 = OpTypeVector %7 2
         %15 = OpTypeStruct %13 %14
         %16 = OpTypeStruct %9 %12 %15
         %17 = OpTypePointer Function %16
         %19 = OpConstantTrue %6
         %20 = OpConstantFalse %6
         %21 = OpConstantComposite %9 %19 %20 %19 %20 %19
         %22 = OpConstant %10 1
         %23 = OpConstant %10 2
         %24 = OpConstantComposite %11 %22 %23
         %25 = OpConstant %10 3
         %26 = OpConstant %10 4
         %27 = OpConstantComposite %11 %25 %26
         %28 = OpConstant %10 5
         %29 = OpConstant %10 6
         %30 = OpConstantComposite %11 %28 %29
         %31 = OpConstant %10 7
         %32 = OpConstant %10 8
         %33 = OpConstantComposite %11 %31 %32
         %34 = OpConstantComposite %12 %24 %27 %30 %33
         %35 = OpConstant %13 10
         %36 = OpConstant %7 100
         %37 = OpConstant %7 200
         %38 = OpConstantComposite %14 %36 %37
         %39 = OpConstantComposite %15 %35 %38
         %40 = OpConstantComposite %16 %21 %34 %39
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %18 = OpVariable %17 Function
               OpStore %18 %40
        %100 = OpCompositeConstruct %9 %19 %20 %19 %20 %19
        %101 = OpCompositeConstruct %11 %22 %23
        %102 = OpCompositeConstruct %11 %25 %26
        %103 = OpCompositeConstruct %11 %28 %29
        %104 = OpCompositeConstruct %11 %31 %32
        %105 = OpCompositeConstruct %12 %101 %102 %103 %104
        %106 = OpCompositeConstruct %14 %36 %37
        %107 = OpCompositeConstruct %15 %35 %106
        %108 = OpCompositeConstruct %16 %100 %105 %107
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  ASSERT_TRUE(IsValid(env, context.get()));

  FactManager fact_manager;
  spvtools::ValidatorOptions validator_options;
  TransformationContext transformation_context(&fact_manager,
                                               validator_options);

  ASSERT_TRUE(TransformationComputeDataSynonymFactClosure(100).IsApplicable(
      context.get(), transformation_context));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
                                         MakeDataDescriptor(101, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
                                         MakeDataDescriptor(101, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {1}),
                                         MakeDataDescriptor(101, {1})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
                                         MakeDataDescriptor(101, {1})));

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(24, {}),
                                  MakeDataDescriptor(101, {}), context.get());
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
                                        MakeDataDescriptor(101, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
                                        MakeDataDescriptor(101, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {1}),
                                        MakeDataDescriptor(101, {1})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
                                         MakeDataDescriptor(101, {1})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
                                         MakeDataDescriptor(102, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
                                         MakeDataDescriptor(102, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
                                         MakeDataDescriptor(102, {1})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {0}),
                                  MakeDataDescriptor(102, {0}), context.get());
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
                                         MakeDataDescriptor(102, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
                                        MakeDataDescriptor(102, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
                                         MakeDataDescriptor(102, {1})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {1}),
                                  MakeDataDescriptor(102, {1}), context.get());

  TransformationComputeDataSynonymFactClosure(100).Apply(
      context.get(), &transformation_context);

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
                                        MakeDataDescriptor(102, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
                                        MakeDataDescriptor(102, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
                                        MakeDataDescriptor(102, {1})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
                                         MakeDataDescriptor(103, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
                                         MakeDataDescriptor(103, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
                                         MakeDataDescriptor(103, {1})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
                                         MakeDataDescriptor(104, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
                                         MakeDataDescriptor(104, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {1}),
                                         MakeDataDescriptor(104, {1})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {}),
                                         MakeDataDescriptor(105, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {0}),
                                         MakeDataDescriptor(105, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {1}),
                                         MakeDataDescriptor(105, {1})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {2}),
                                         MakeDataDescriptor(105, {2})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
                                         MakeDataDescriptor(105, {3})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(30, {}),
                                  MakeDataDescriptor(103, {}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(33, {}),
                                  MakeDataDescriptor(104, {}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {0}),
                                  MakeDataDescriptor(105, {0}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {1}),
                                  MakeDataDescriptor(105, {1}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {2}),
                                  MakeDataDescriptor(105, {2}), context.get());
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
                                        MakeDataDescriptor(103, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
                                        MakeDataDescriptor(103, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
                                        MakeDataDescriptor(103, {1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
                                        MakeDataDescriptor(104, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
                                        MakeDataDescriptor(104, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {1}),
                                        MakeDataDescriptor(104, {1})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {}),
                                         MakeDataDescriptor(105, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {0}),
                                        MakeDataDescriptor(105, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {1}),
                                        MakeDataDescriptor(105, {1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {2}),
                                        MakeDataDescriptor(105, {2})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
                                         MakeDataDescriptor(105, {3})));

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {3}),
                                  MakeDataDescriptor(105, {3}), context.get());
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
                                        MakeDataDescriptor(104, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
                                        MakeDataDescriptor(105, {3})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
                                         MakeDataDescriptor(100, {})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {0}),
                                  MakeDataDescriptor(100, {0}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {1}),
                                  MakeDataDescriptor(100, {1}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {2}),
                                  MakeDataDescriptor(100, {2}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {3}),
                                  MakeDataDescriptor(100, {3}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {4}),
                                  MakeDataDescriptor(100, {4}), context.get());

  TransformationComputeDataSynonymFactClosure(100).Apply(
      context.get(), &transformation_context);

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
                                        MakeDataDescriptor(100, {})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}),
                                         MakeDataDescriptor(107, {0})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(35, {}),
                                         MakeDataDescriptor(39, {0})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(39, {0}),
                                  MakeDataDescriptor(35, {}), context.get());
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}),
                                         MakeDataDescriptor(107, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(35, {}),
                                        MakeDataDescriptor(39, {0})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {0}),
                                         MakeDataDescriptor(36, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {1}),
                                         MakeDataDescriptor(37, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {0}),
                                         MakeDataDescriptor(36, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {1}),
                                         MakeDataDescriptor(37, {})));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {}),
                                         MakeDataDescriptor(106, {})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {0}),
                                  MakeDataDescriptor(36, {}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {0}),
                                  MakeDataDescriptor(36, {}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {1}),
                                  MakeDataDescriptor(37, {}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {1}),
                                  MakeDataDescriptor(37, {}), context.get());

  TransformationComputeDataSynonymFactClosure(100).Apply(
      context.get(), &transformation_context);

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {0}),
                                        MakeDataDescriptor(36, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {1}),
                                        MakeDataDescriptor(37, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {0}),
                                        MakeDataDescriptor(36, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {1}),
                                        MakeDataDescriptor(37, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {}),
                                        MakeDataDescriptor(106, {})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}),
                                         MakeDataDescriptor(108, {})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(107, {0}),
                                  MakeDataDescriptor(35, {}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {0}),
                                  MakeDataDescriptor(108, {0}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {1}),
                                  MakeDataDescriptor(108, {1}), context.get());
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {2}),
                                  MakeDataDescriptor(108, {2}), context.get());

  TransformationComputeDataSynonymFactClosure(100).Apply(
      context.get(), &transformation_context);

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}),
                                        MakeDataDescriptor(108, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0}),
                                        MakeDataDescriptor(108, {0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1}),
                                        MakeDataDescriptor(108, {1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2}),
                                        MakeDataDescriptor(108, {2})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 0}),
                                        MakeDataDescriptor(108, {0, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 1}),
                                        MakeDataDescriptor(108, {0, 1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 2}),
                                        MakeDataDescriptor(108, {0, 2})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 3}),
                                        MakeDataDescriptor(108, {0, 3})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 4}),
                                        MakeDataDescriptor(108, {0, 4})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0}),
                                        MakeDataDescriptor(108, {1, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1}),
                                        MakeDataDescriptor(108, {1, 1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2}),
                                        MakeDataDescriptor(108, {1, 2})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3}),
                                        MakeDataDescriptor(108, {1, 3})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0, 0}),
                                        MakeDataDescriptor(108, {1, 0, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1, 0}),
                                        MakeDataDescriptor(108, {1, 1, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2, 0}),
                                        MakeDataDescriptor(108, {1, 2, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3, 0}),
                                        MakeDataDescriptor(108, {1, 3, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0, 1}),
                                        MakeDataDescriptor(108, {1, 0, 1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1, 1}),
                                        MakeDataDescriptor(108, {1, 1, 1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2, 1}),
                                        MakeDataDescriptor(108, {1, 2, 1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3, 1}),
                                        MakeDataDescriptor(108, {1, 3, 1})));

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 0}),
                                        MakeDataDescriptor(108, {2, 0})));

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1}),
                                        MakeDataDescriptor(108, {2, 1})));

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1, 0}),
                                        MakeDataDescriptor(108, {2, 1, 0})));

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1, 1}),
                                        MakeDataDescriptor(108, {2, 1, 1})));
}

}  // namespace
}  // namespace fuzz
}  // namespace spvtools
