// Copyright (c) 2019 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/fact_manager/fact_manager.h"

#include "gtest/gtest.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_merge_blocks.h"
#include "test/fuzz/fuzz_test_util.h"

namespace spvtools {
namespace fuzz {
namespace {

void CheckConsistencyOfSynonymFacts(
    opt::IRContext* ir_context,
    const TransformationContext& transformation_context) {
  for (uint32_t id : transformation_context.GetFactManager()
                         ->GetIdsForWhichSynonymsAreKnown()) {
    // Every id reported by the fact manager should exist in the module.
    ASSERT_NE(ir_context->get_def_use_mgr()->GetDef(id), nullptr);
    auto synonyms =
        transformation_context.GetFactManager()->GetSynonymsForId(id);
    for (auto& dd : synonyms) {
      // Every reported synonym should have a base object that exists in the
      // module.
      ASSERT_NE(ir_context->get_def_use_mgr()->GetDef(dd->object()), nullptr);
    }
  }
}

TEST(DataSynonymAndIdEquationFactsTest, RecursiveAdditionOfFacts) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeFloat 32
          %7 = OpTypeVector %6 4
          %8 = OpTypeMatrix %7 4
          %9 = OpConstant %6 0
         %10 = OpConstantComposite %7 %9 %9 %9 %9
         %11 = OpConstantComposite %8 %10 %10 %10 %10
         %12 = OpFunction %2 None %3
         %13 = OpLabel
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
                                  MakeDataDescriptor(11, {2}));

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}),
                                        MakeDataDescriptor(11, {2})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {0}),
                                        MakeDataDescriptor(11, {2, 0})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {1}),
                                        MakeDataDescriptor(11, {2, 1})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {2}),
                                        MakeDataDescriptor(11, {2, 2})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {3}),
                                        MakeDataDescriptor(11, {2, 3})));
}

TEST(DataSynonymAndIdEquationFactsTest, CorollaryConversionFacts) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypeInt 32 0
          %8 = OpTypeVector %6 2
          %9 = OpTypeVector %7 2
         %10 = OpTypeFloat 32
         %11 = OpTypeVector %10 2
         %15 = OpConstant %6 24 ; synonym of %16
         %16 = OpConstant %6 24
         %17 = OpConstant %7 24 ; synonym of %18
         %18 = OpConstant %7 24
         %19 = OpConstantComposite %8 %15 %15 ; synonym of %20
         %20 = OpConstantComposite %8 %16 %16
         %21 = OpConstantComposite %9 %17 %17 ; synonym of %22
         %22 = OpConstantComposite %9 %18 %18
         %23 = OpConstantComposite %8 %15 %15 ; not a synonym of %19
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %24 = OpConvertSToF %10 %15 ; synonym of %25
         %25 = OpConvertSToF %10 %16
         %26 = OpConvertUToF %10 %17 ; not a synonym of %27 (different opcode)
         %27 = OpConvertSToF %10 %18
         %28 = OpConvertUToF %11 %19 ; synonym of %29
         %29 = OpConvertUToF %11 %20
         %30 = OpConvertSToF %11 %21 ; not a synonym of %31 (different opcode)
         %31 = OpConvertUToF %11 %22
         %32 = OpConvertUToF %11 %23 ; not a synonym of %28 (operand is not synonymous)
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  // Add equation facts
  fact_manager.AddFactIdEquation(24, spv::Op::OpConvertSToF, {15});
  fact_manager.AddFactIdEquation(25, spv::Op::OpConvertSToF, {16});
  fact_manager.AddFactIdEquation(26, spv::Op::OpConvertUToF, {17});
  fact_manager.AddFactIdEquation(27, spv::Op::OpConvertSToF, {18});
  fact_manager.AddFactIdEquation(28, spv::Op::OpConvertUToF, {19});
  fact_manager.AddFactIdEquation(29, spv::Op::OpConvertUToF, {20});
  fact_manager.AddFactIdEquation(30, spv::Op::OpConvertSToF, {21});
  fact_manager.AddFactIdEquation(31, spv::Op::OpConvertUToF, {22});
  fact_manager.AddFactIdEquation(32, spv::Op::OpConvertUToF, {23});

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}),
                                  MakeDataDescriptor(16, {}));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
                                        MakeDataDescriptor(25, {})));

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}),
                                  MakeDataDescriptor(18, {}));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}),
                                         MakeDataDescriptor(27, {})));

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(19, {}),
                                  MakeDataDescriptor(20, {}));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}),
                                        MakeDataDescriptor(29, {})));

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}),
                                  MakeDataDescriptor(22, {}));
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
                                         MakeDataDescriptor(31, {})));

  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
                                         MakeDataDescriptor(28, {})));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}),
                                  MakeDataDescriptor(19, {}));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
                                        MakeDataDescriptor(28, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
                                        MakeDataDescriptor(29, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, HandlesCorollariesWithInvalidIds) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeFloat 32
          %8 = OpTypeInt 32 1
          %9 = OpConstant %8 3
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpConvertSToF %6 %9
               OpBranch %16
         %16 = OpLabel
         %17 = OpPhi %6 %14 %13
         %15 = OpConvertSToF %6 %9
         %18 = OpConvertSToF %6 %9
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);

  // Add required facts.
  transformation_context.GetFactManager()->AddFactIdEquation(
      14, spv::Op::OpConvertSToF, {9});
  transformation_context.GetFactManager()->AddFactDataSynonym(
      MakeDataDescriptor(14, {}), MakeDataDescriptor(17, {}));

  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);

  // Apply TransformationMergeBlocks which will remove %17 from the module.
  TransformationMergeBlocks transformation(16);
  ASSERT_TRUE(
      transformation.IsApplicable(context.get(), transformation_context));
  transformation.Apply(context.get(), &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);

  ASSERT_EQ(context->get_def_use_mgr()->GetDef(17), nullptr);

  // Add another equation.
  transformation_context.GetFactManager()->AddFactIdEquation(
      15, spv::Op::OpConvertSToF, {9});

  // Check that two ids are synonymous even though one of them doesn't exist in
  // the module (%17).
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(15, {}), MakeDataDescriptor(17, {})));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(15, {}), MakeDataDescriptor(14, {})));

  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);

  // Remove some instructions from the module. At this point, the equivalence
  // class of %14 has no valid members.
  ASSERT_TRUE(context->KillDef(14));
  ASSERT_TRUE(context->KillDef(15));

  transformation_context.GetFactManager()->AddFactIdEquation(
      18, spv::Op::OpConvertSToF, {9});

  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);

  // We don't create synonyms if at least one of the equivalence classes has no
  // valid members.
  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(14, {}), MakeDataDescriptor(18, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, LogicalNotEquationFacts) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeBool
          %7 = OpConstantTrue %6
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpLogicalNot %6 %7
         %15 = OpCopyObject %6 %7
         %16 = OpCopyObject %6 %14
         %17 = OpLogicalNot %6 %16
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}),
                                  MakeDataDescriptor(7, {}));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}),
                                  MakeDataDescriptor(14, {}));
  fact_manager.AddFactIdEquation(14, spv::Op::OpLogicalNot, {7});
  fact_manager.AddFactIdEquation(17, spv::Op::OpLogicalNot, {16});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}),
                                        MakeDataDescriptor(7, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
                                        MakeDataDescriptor(7, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}),
                                        MakeDataDescriptor(17, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(16, {}),
                                        MakeDataDescriptor(14, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, SignedNegateEquationFacts) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpConstant %6 24
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpSNegate %6 %7
         %15 = OpSNegate %6 %14
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactIdEquation(14, spv::Op::OpSNegate, {7});
  fact_manager.AddFactIdEquation(15, spv::Op::OpSNegate, {14});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
                                        MakeDataDescriptor(15, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, AddSubNegateFacts1) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpIAdd %6 %15 %16
         %17 = OpCopyObject %6 %15
         %18 = OpCopyObject %6 %16
         %19 = OpISub %6 %14 %18 ; ==> synonymous(%19, %15)
         %20 = OpISub %6 %14 %17 ; ==> synonymous(%20, %16)
         %21 = OpCopyObject %6 %14
         %22 = OpISub %6 %16 %21
         %23 = OpCopyObject %6 %22
         %24 = OpSNegate %6 %23 ; ==> synonymous(%24, %15)
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactIdEquation(14, spv::Op::OpIAdd, {15, 16});
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}),
                                  MakeDataDescriptor(15, {}));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}),
                                  MakeDataDescriptor(16, {}));
  fact_manager.AddFactIdEquation(19, spv::Op::OpISub, {14, 18});
  fact_manager.AddFactIdEquation(20, spv::Op::OpISub, {14, 17});
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}),
                                  MakeDataDescriptor(14, {}));
  fact_manager.AddFactIdEquation(22, spv::Op::OpISub, {16, 21});
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}),
                                  MakeDataDescriptor(22, {}));
  fact_manager.AddFactIdEquation(24, spv::Op::OpSNegate, {23});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(19, {}),
                                        MakeDataDescriptor(15, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
                                        MakeDataDescriptor(16, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
                                        MakeDataDescriptor(15, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, AddSubNegateFacts2) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpISub %6 %15 %16
         %17 = OpIAdd %6 %14 %16 ; ==> synonymous(%17, %15)
         %18 = OpIAdd %6 %16 %14 ; ==> synonymous(%17, %18, %15)
         %19 = OpISub %6 %14 %15
         %20 = OpSNegate %6 %19 ; ==> synonymous(%20, %16)
         %21 = OpISub %6 %14 %19 ; ==> synonymous(%21, %15)
         %22 = OpISub %6 %14 %18
         %23 = OpSNegate %6 %22 ; ==> synonymous(%23, %16)
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactIdEquation(14, spv::Op::OpISub, {15, 16});
  fact_manager.AddFactIdEquation(17, spv::Op::OpIAdd, {14, 16});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
                                        MakeDataDescriptor(15, {})));

  fact_manager.AddFactIdEquation(18, spv::Op::OpIAdd, {16, 14});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
                                        MakeDataDescriptor(15, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
                                        MakeDataDescriptor(18, {})));

  fact_manager.AddFactIdEquation(19, spv::Op::OpISub, {14, 15});
  fact_manager.AddFactIdEquation(20, spv::Op::OpSNegate, {19});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
                                        MakeDataDescriptor(16, {})));

  fact_manager.AddFactIdEquation(21, spv::Op::OpISub, {14, 19});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
                                        MakeDataDescriptor(15, {})));

  fact_manager.AddFactIdEquation(22, spv::Op::OpISub, {14, 18});
  fact_manager.AddFactIdEquation(23, spv::Op::OpSNegate, {22});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}),
                                        MakeDataDescriptor(16, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, ConversionEquations) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %4 = OpTypeInt 32 1
          %5 = OpTypeInt 32 0
          %6 = OpTypeFloat 32
         %14 = OpTypeVector %4 2
         %15 = OpTypeVector %5 2
         %24 = OpTypeVector %6 2
         %16 = OpConstant %4 32 ; synonym of %17
         %17 = OpConstant %4 32
         %18 = OpConstant %5 32 ; synonym of %19
         %19 = OpConstant %5 32
         %20 = OpConstantComposite %14 %16 %16 ; synonym of %21
         %21 = OpConstantComposite %14 %17 %17
         %22 = OpConstantComposite %15 %18 %18 ; synonym of %23
         %23 = OpConstantComposite %15 %19 %19
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %25 = OpConvertUToF %6 %16 ; synonym of %26
         %26 = OpConvertUToF %6 %17
         %27 = OpConvertSToF %24 %20 ; not a synonym of %28 (wrong opcode)
         %28 = OpConvertUToF %24 %21
         %29 = OpConvertSToF %6 %18 ; not a synonym of %30 (wrong opcode)
         %30 = OpConvertUToF %6 %19
         %31 = OpConvertSToF %24 %22 ; synonym of %32
         %32 = OpConvertSToF %24 %23
         %33 = OpConvertUToF %6 %17 ; synonym of %26
         %34 = OpConvertSToF %24 %23 ; synonym of %32
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}),
                                  MakeDataDescriptor(17, {}));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}),
                                  MakeDataDescriptor(19, {}));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(20, {}),
                                  MakeDataDescriptor(21, {}));
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}),
                                  MakeDataDescriptor(23, {}));

  fact_manager.AddFactIdEquation(25, spv::Op::OpConvertUToF, {16});
  fact_manager.AddFactIdEquation(26, spv::Op::OpConvertUToF, {17});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
                                        MakeDataDescriptor(26, {})));

  fact_manager.AddFactIdEquation(27, spv::Op::OpConvertSToF, {20});
  fact_manager.AddFactIdEquation(28, spv::Op::OpConvertUToF, {21});
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
                                         MakeDataDescriptor(28, {})));

  fact_manager.AddFactIdEquation(29, spv::Op::OpConvertSToF, {18});
  fact_manager.AddFactIdEquation(30, spv::Op::OpConvertUToF, {19});
  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(29, {}),
                                         MakeDataDescriptor(30, {})));

  fact_manager.AddFactIdEquation(31, spv::Op::OpConvertSToF, {22});
  fact_manager.AddFactIdEquation(32, spv::Op::OpConvertSToF, {23});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(31, {}),
                                        MakeDataDescriptor(32, {})));

  fact_manager.AddFactIdEquation(33, spv::Op::OpConvertUToF, {17});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
                                        MakeDataDescriptor(26, {})));

  fact_manager.AddFactIdEquation(34, spv::Op::OpConvertSToF, {23});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
                                        MakeDataDescriptor(34, {})));
}

TEST(DataSynonymAndIdEquationFactsTest, BitcastEquationFacts) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %4 = OpTypeInt 32 1
          %5 = OpTypeInt 32 0
          %8 = OpTypeFloat 32
          %9 = OpTypeVector %4 2
         %10 = OpTypeVector %5 2
         %11 = OpTypeVector %8 2
          %6 = OpConstant %4 23
          %7 = OpConstant %5 23
         %19 = OpConstant %8 23
         %20 = OpConstantComposite %9 %6 %6
         %21 = OpConstantComposite %10 %7 %7
         %22 = OpConstantComposite %11 %19 %19
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %30 = OpBitcast %8 %6
         %31 = OpBitcast %5 %6
         %32 = OpBitcast %8 %7
         %33 = OpBitcast %4 %7
         %34 = OpBitcast %4 %19
         %35 = OpBitcast %5 %19
         %36 = OpBitcast %10 %20
         %37 = OpBitcast %11 %20
         %38 = OpBitcast %9 %21
         %39 = OpBitcast %11 %21
         %40 = OpBitcast %9 %22
         %41 = OpBitcast %10 %22
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  uint32_t lhs_id = 30;
  for (uint32_t rhs_id : {6, 6, 7, 7, 19, 19, 20, 20, 21, 21, 22, 22}) {
    fact_manager.AddFactIdEquation(lhs_id, spv::Op::OpBitcast, {rhs_id});
    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(lhs_id, {}),
                                          MakeDataDescriptor(rhs_id, {})));
    ++lhs_id;
  }
}

TEST(DataSynonymAndIdEquationFactsTest, EquationAndEquivalenceFacts) {
  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %12 "main"
               OpExecutionMode %12 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpISub %6 %15 %16
        %114 = OpCopyObject %6 %14
         %17 = OpIAdd %6 %114 %16 ; ==> synonymous(%17, %15)
         %18 = OpIAdd %6 %16 %114 ; ==> synonymous(%17, %18, %15)
         %19 = OpISub %6 %114 %15
        %119 = OpCopyObject %6 %19
         %20 = OpSNegate %6 %119 ; ==> synonymous(%20, %16)
         %21 = OpISub %6 %14 %19 ; ==> synonymous(%21, %15)
         %22 = OpISub %6 %14 %18
        %220 = OpCopyObject %6 %22
         %23 = OpSNegate %6 %220 ; ==> synonymous(%23, %16)
               OpReturn
               OpFunctionEnd
  )";

  const auto env = SPV_ENV_UNIVERSAL_1_3;
  const auto consumer = nullptr;
  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
  spvtools::ValidatorOptions validator_options;
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  FactManager fact_manager(context.get());

  fact_manager.AddFactIdEquation(14, spv::Op::OpISub, {15, 16});
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(114, {}),
                                  MakeDataDescriptor(14, {}));
  fact_manager.AddFactIdEquation(17, spv::Op::OpIAdd, {114, 16});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
                                        MakeDataDescriptor(15, {})));

  fact_manager.AddFactIdEquation(18, spv::Op::OpIAdd, {16, 114});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
                                        MakeDataDescriptor(15, {})));
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
                                        MakeDataDescriptor(18, {})));

  fact_manager.AddFactIdEquation(19, spv::Op::OpISub, {14, 15});
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(119, {}),
                                  MakeDataDescriptor(19, {}));
  fact_manager.AddFactIdEquation(20, spv::Op::OpSNegate, {119});

  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
                                        MakeDataDescriptor(16, {})));

  fact_manager.AddFactIdEquation(21, spv::Op::OpISub, {14, 19});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
                                        MakeDataDescriptor(15, {})));

  fact_manager.AddFactIdEquation(22, spv::Op::OpISub, {14, 18});
  fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}),
                                  MakeDataDescriptor(220, {}));
  fact_manager.AddFactIdEquation(23, spv::Op::OpSNegate, {220});
  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}),
                                        MakeDataDescriptor(16, {})));
}

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