// 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_equation_instruction.h"

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

namespace spvtools {
namespace fuzz {
namespace {

TEST(TransformationEquationInstructionTest, SignedNegate) {
  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
         %40 = OpTypeBool
         %41 = OpConstantTrue %40
         %20 = OpUndef %6
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %30 = OpCopyObject %6 %7
               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);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Bad: id already in use.
  ASSERT_FALSE(TransformationEquationInstruction(7, spv::Op::OpSNegate, {7},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: identified instruction does not exist.
  ASSERT_FALSE(TransformationEquationInstruction(
                   14, spv::Op::OpSNegate, {7},
                   MakeInstructionDescriptor(13, spv::Op::OpLoad, 0))
                   .IsApplicable(context.get(), transformation_context));

  // Bad: id 100 does not exist
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpSNegate, {100},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: id 20 is an OpUndef
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpSNegate, {20},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: id 30 is not available right before its definition
  ASSERT_FALSE(TransformationEquationInstruction(
                   14, spv::Op::OpSNegate, {30},
                   MakeInstructionDescriptor(30, spv::Op::OpCopyObject, 0))
                   .IsApplicable(context.get(), transformation_context));

  // Bad: too many arguments to OpSNegate.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpSNegate, {7, 7},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: 40 is a type id.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpSNegate, {40},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: wrong type of argument to OpSNegate.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpSNegate, {41},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  auto transformation1 = TransformationEquationInstruction(
      14, spv::Op::OpSNegate, {7}, return_instruction);
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(14));
  ASSERT_EQ(nullptr, context->get_instr_block(14));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_EQ(spv::Op::OpSNegate,
            context->get_def_use_mgr()->GetDef(14)->opcode());
  ASSERT_EQ(13, context->get_instr_block(14)->id());
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation2 = TransformationEquationInstruction(
      15, spv::Op::OpSNegate, {14}, return_instruction);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {})));

  std::string after_transformation = 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
         %40 = OpTypeBool
         %41 = OpConstantTrue %40
         %20 = OpUndef %6
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %30 = OpCopyObject %6 %7
         %14 = OpSNegate %6 %7
         %15 = OpSNegate %6 %14
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}

TEST(TransformationEquationInstructionTest, LogicalNot) {
  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
         %20 = OpTypeInt 32 0
         %21 = OpConstant %20 5
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Bad: too few arguments to OpLogicalNot.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpLogicalNot, {},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: 6 is a type id.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpLogicalNot, {6},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Bad: wrong type of argument to OpLogicalNot.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpLogicalNot,
                                                 {21}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  auto transformation1 = TransformationEquationInstruction(
      14, spv::Op::OpLogicalNot, {7}, return_instruction);
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation2 = TransformationEquationInstruction(
      15, spv::Op::OpLogicalNot, {14}, return_instruction);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {})));

  std::string after_transformation = 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
         %20 = OpTypeInt 32 0
         %21 = OpConstant %20 5
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpLogicalNot %6 %7
         %15 = OpLogicalNot %6 %14
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}

TEST(TransformationEquationInstructionTest, AddSubNegate1) {
  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
         %30 = OpTypeVector %6 3
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %31 = OpConstantComposite %30 %15 %16 %15
         %33 = OpTypeBool
         %32 = OpConstantTrue %33
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Bad: too many arguments to OpIAdd.
  ASSERT_FALSE(TransformationEquationInstruction(
                   14, spv::Op::OpIAdd, {15, 16, 16}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  // Bad: boolean argument to OpIAdd.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpIAdd, {15, 32},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  // Bad: type as argument to OpIAdd.
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpIAdd, {33, 16},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  // Bad: arguments of mismatched widths
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpIAdd, {15, 31},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  // Bad: arguments of mismatched widths
  ASSERT_FALSE(TransformationEquationInstruction(14, spv::Op::OpIAdd, {31, 15},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  auto transformation1 = TransformationEquationInstruction(
      14, spv::Op::OpIAdd, {15, 16}, return_instruction);
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation2 = TransformationEquationInstruction(
      19, spv::Op::OpISub, {14, 16}, return_instruction);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(15, {}), MakeDataDescriptor(19, {})));

  auto transformation3 = TransformationEquationInstruction(
      20, spv::Op::OpISub, {14, 15}, return_instruction);
  ASSERT_TRUE(
      transformation3.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation3, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {})));

  auto transformation4 = TransformationEquationInstruction(
      22, spv::Op::OpISub, {16, 14}, return_instruction);
  ASSERT_TRUE(
      transformation4.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation4, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation5 = TransformationEquationInstruction(
      24, spv::Op::OpSNegate, {22}, return_instruction);
  ASSERT_TRUE(
      transformation5.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation5, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(24, {}), MakeDataDescriptor(15, {})));

  std::string after_transformation = 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
         %30 = OpTypeVector %6 3
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %31 = OpConstantComposite %30 %15 %16 %15
         %33 = OpTypeBool
         %32 = OpConstantTrue %33
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %14 = OpIAdd %6 %15 %16
         %19 = OpISub %6 %14 %16 ; ==> synonymous(%19, %15)
         %20 = OpISub %6 %14 %15 ; ==> synonymous(%20, %16)
         %22 = OpISub %6 %16 %14
         %24 = OpSNegate %6 %22 ; ==> synonymous(%24, %15)
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}

TEST(TransformationEquationInstructionTest, AddSubNegate2) {
  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
               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);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  auto transformation1 = TransformationEquationInstruction(
      14, spv::Op::OpISub, {15, 16}, return_instruction);
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation2 = TransformationEquationInstruction(
      17, spv::Op::OpIAdd, {14, 16}, return_instruction);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(17, {}), MakeDataDescriptor(15, {})));

  auto transformation3 = TransformationEquationInstruction(
      18, spv::Op::OpIAdd, {16, 14}, return_instruction);
  ASSERT_TRUE(
      transformation3.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation3, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {})));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(18, {}), MakeDataDescriptor(15, {})));

  auto transformation4 = TransformationEquationInstruction(
      19, spv::Op::OpISub, {14, 15}, return_instruction);
  ASSERT_TRUE(
      transformation4.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation4, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation5 = TransformationEquationInstruction(
      20, spv::Op::OpSNegate, {19}, return_instruction);
  ASSERT_TRUE(
      transformation5.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation5, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {})));

  auto transformation6 = TransformationEquationInstruction(
      21, spv::Op::OpISub, {14, 19}, return_instruction);
  ASSERT_TRUE(
      transformation6.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation6, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {})));

  auto transformation7 = TransformationEquationInstruction(
      22, spv::Op::OpISub, {14, 18}, return_instruction);
  ASSERT_TRUE(
      transformation7.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation7, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation8 = TransformationEquationInstruction(
      23, spv::Op::OpSNegate, {22}, return_instruction);
  ASSERT_TRUE(
      transformation8.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation8, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));
  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(23, {}), MakeDataDescriptor(16, {})));

  std::string after_transformation = 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
  )";

  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}

TEST(TransformationEquationInstructionTest, Bitcast) {
  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 = OpTypeFloat 32
          %9 = OpTypeVector %6 2
         %10 = OpTypeVector %7 2
         %11 = OpTypeVector %8 2
         %21 = OpTypeBool
         %22 = OpTypeVector %21 2
         %15 = OpConstant %6 24
         %16 = OpConstant %7 24
         %17 = OpConstant %8 24
         %18 = OpConstantComposite %9 %15 %15
         %19 = OpConstantComposite %10 %16 %16
         %20 = OpConstantComposite %11 %17 %17
         %23 = OpConstantTrue %21
         %24 = OpConstantComposite %22 %23 %23
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Too many operands.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast,
                                                 {15, 16}, insert_before)
                   .IsApplicable(context.get(), transformation_context));

  // Too few operands.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  // Operand's id is invalid.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {50},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  // Operand's type is invalid
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {13},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  // Operand must be a scalar or a vector of numerical type.
#ifndef NDEBUG
  ASSERT_DEATH(TransformationEquationInstruction(50, spv::Op::OpBitcast, {23},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context),
               "Operand is not a scalar or a vector of numerical type");
  ASSERT_DEATH(TransformationEquationInstruction(50, spv::Op::OpBitcast, {24},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context),
               "Only vectors of numerical components are supported");
#else
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {23},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {24},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));
#endif

  for (uint32_t operand_id = 15, fresh_id = 50; operand_id <= 20;
       ++operand_id, ++fresh_id) {
    TransformationEquationInstruction transformation(
        fresh_id, spv::Op::OpBitcast, {operand_id}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  std::string expected_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 = OpTypeFloat 32
          %9 = OpTypeVector %6 2
         %10 = OpTypeVector %7 2
         %11 = OpTypeVector %8 2
         %21 = OpTypeBool
         %22 = OpTypeVector %21 2
         %15 = OpConstant %6 24
         %16 = OpConstant %7 24
         %17 = OpConstant %8 24
         %18 = OpConstantComposite %9 %15 %15
         %19 = OpConstantComposite %10 %16 %16
         %20 = OpConstantComposite %11 %17 %17
         %23 = OpConstantTrue %21
         %24 = OpConstantComposite %22 %23 %23
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %8 %15
         %51 = OpBitcast %8 %16
         %52 = OpBitcast %6 %17
         %53 = OpBitcast %11 %18
         %54 = OpBitcast %11 %19
         %55 = OpBitcast %9 %20
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest,
     BitcastResultTypeFloatDoesNotExist) {
  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
          %9 = OpTypeVector %6 2
         %10 = OpTypeVector %7 2
         %15 = OpConstant %6 24
         %16 = OpConstant %7 24
         %18 = OpConstantComposite %9 %15 %15
         %19 = OpConstantComposite %10 %16 %16
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Scalar floating-point type does not exist.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {15},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {16},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  // Vector of floating-point components does not exist.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {18},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {19},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist1) {
  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
          %8 = OpTypeFloat 32
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Scalar integral type does not exist.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {17},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  // Vector of integral components does not exist.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpBitcast, {20},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist2) {
  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 0
          %8 = OpTypeFloat 32
          %9 = OpTypeVector %4 2
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpBitcast,
                                                     {17}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(51, spv::Op::OpBitcast,
                                                     {20}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  std::string expected_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 0
          %8 = OpTypeFloat 32
          %9 = OpTypeVector %4 2
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %4 %17
         %51 = OpBitcast %9 %20
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist3) {
  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
          %8 = OpTypeFloat 32
          %9 = OpTypeVector %4 2
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpBitcast,
                                                     {17}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(51, spv::Op::OpBitcast,
                                                     {20}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  std::string expected_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
          %8 = OpTypeFloat 32
          %9 = OpTypeVector %4 2
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %4 %17
         %51 = OpBitcast %9 %20
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist4) {
  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
          %8 = OpTypeFloat 32
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpBitcast,
                                                     {17}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  ASSERT_FALSE(TransformationEquationInstruction(51, spv::Op::OpBitcast, {20},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string expected_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
          %8 = OpTypeFloat 32
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %4 %17
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist5) {
  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 0
          %8 = OpTypeFloat 32
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpBitcast,
                                                     {17}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  ASSERT_FALSE(TransformationEquationInstruction(51, spv::Op::OpBitcast, {20},
                                                 insert_before)
                   .IsApplicable(context.get(), transformation_context));

  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string expected_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 0
          %8 = OpTypeFloat 32
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %4 %17
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist6) {
  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 %5 2
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpBitcast,
                                                     {17}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(51, spv::Op::OpBitcast,
                                                     {20}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string expected_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 %5 2
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %4 %17
         %51 = OpBitcast %9 %20
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest, BitcastResultTypeIntDoesNotExist7) {
  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
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto insert_before = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpBitcast,
                                                     {17}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(51, spv::Op::OpBitcast,
                                                     {20}, insert_before);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string expected_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
         %11 = OpTypeVector %8 2
         %17 = OpConstant %8 24
         %20 = OpConstantComposite %11 %17 %17
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpBitcast %4 %17
         %51 = OpBitcast %9 %20
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
}

TEST(TransformationEquationInstructionTest, Miscellaneous1) {
  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
        %113 = OpConstant %6 24
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  auto transformation1 = TransformationEquationInstruction(
      522, spv::Op::OpISub, {113, 113}, return_instruction);
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation2 = TransformationEquationInstruction(
      570, spv::Op::OpIAdd, {522, 113}, return_instruction);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string after_transformation = 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
        %113 = OpConstant %6 24
         %12 = OpFunction %2 None %3
         %13 = OpLabel
        %522 = OpISub %6 %113 %113
        %570 = OpIAdd %6 %522 %113
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(570, {}), MakeDataDescriptor(113, {})));

  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}

TEST(TransformationEquationInstructionTest, Miscellaneous2) {
  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
        %113 = OpConstant %6 24
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  auto transformation1 = TransformationEquationInstruction(
      522, spv::Op::OpISub, {113, 113}, return_instruction);
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  auto transformation2 = TransformationEquationInstruction(
      570, spv::Op::OpIAdd, {522, 113}, return_instruction);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string after_transformation = 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
        %113 = OpConstant %6 24
         %12 = OpFunction %2 None %3
         %13 = OpLabel
        %522 = OpISub %6 %113 %113
        %570 = OpIAdd %6 %522 %113
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(570, {}), MakeDataDescriptor(113, {})));

  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}

TEST(TransformationEquationInstructionTest, ConversionInstructions) {
  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
          %4 = OpTypeInt 32 0
          %5 = OpTypeFloat 32
          %7 = OpTypeVector %6 3
          %8 = OpTypeVector %4 3
          %9 = OpTypeVector %5 3
         %10 = OpConstant %6 12
         %20 = OpConstant %6 12
         %11 = OpConstant %4 12
         %21 = OpConstant %4 12
         %14 = OpConstant %5 12
         %15 = OpConstantComposite %7 %10 %10 %10
         %18 = OpConstantComposite %7 %10 %10 %10
         %16 = OpConstantComposite %8 %11 %11 %11
         %19 = OpConstantComposite %8 %11 %11 %11
         %17 = OpConstantComposite %9 %14 %14 %14
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Too few instruction operands.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertSToF, {},
                                                 return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Too many instruction operands.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertSToF,
                                                 {15, 16}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Operand has no type id.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertSToF,
                                                 {7}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // OpConvertSToF and OpConvertUToF require an operand to have scalar or vector
  // of integral components type.
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertSToF,
                                                 {17}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertSToF,
                                                 {14}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertUToF,
                                                 {17}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(50, spv::Op::OpConvertUToF,
                                                 {14}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  {
    TransformationEquationInstruction transformation(50, spv::Op::OpConvertSToF,
                                                     {15}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(51, spv::Op::OpConvertSToF,
                                                     {10}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(52, spv::Op::OpConvertUToF,
                                                     {16}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(53, spv::Op::OpConvertUToF,
                                                     {11}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(58, spv::Op::OpConvertSToF,
                                                     {18}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(59, spv::Op::OpConvertUToF,
                                                     {19}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(60, spv::Op::OpConvertSToF,
                                                     {20}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }
  {
    TransformationEquationInstruction transformation(61, spv::Op::OpConvertUToF,
                                                     {21}, return_instruction);
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
  }

  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  std::string after_transformations = 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
          %4 = OpTypeInt 32 0
          %5 = OpTypeFloat 32
          %7 = OpTypeVector %6 3
          %8 = OpTypeVector %4 3
          %9 = OpTypeVector %5 3
         %10 = OpConstant %6 12
         %20 = OpConstant %6 12
         %11 = OpConstant %4 12
         %21 = OpConstant %4 12
         %14 = OpConstant %5 12
         %15 = OpConstantComposite %7 %10 %10 %10
         %18 = OpConstantComposite %7 %10 %10 %10
         %16 = OpConstantComposite %8 %11 %11 %11
         %19 = OpConstantComposite %8 %11 %11 %11
         %17 = OpConstantComposite %9 %14 %14 %14
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %50 = OpConvertSToF %9 %15
         %51 = OpConvertSToF %5 %10
         %52 = OpConvertUToF %9 %16
         %53 = OpConvertUToF %5 %11
         %58 = OpConvertSToF %9 %18
         %59 = OpConvertUToF %9 %19
         %60 = OpConvertSToF %5 %20
         %61 = OpConvertUToF %5 %21
               OpReturn
               OpFunctionEnd
  )";

  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
}

TEST(TransformationEquationInstructionTest, FloatResultTypeDoesNotExist) {
  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 0
          %7 = OpTypeInt 32 1
          %8 = OpTypeVector %6 3
          %9 = OpTypeVector %7 3
         %10 = OpConstant %6 24
         %11 = OpConstant %7 25
         %14 = OpConstantComposite %8 %10 %10 %10
         %15 = OpConstantComposite %9 %11 %11 %11
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  protobufs::InstructionDescriptor return_instruction =
      MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Scalar float type doesn't exist.
  ASSERT_FALSE(TransformationEquationInstruction(16, spv::Op::OpConvertUToF,
                                                 {10}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(16, spv::Op::OpConvertSToF,
                                                 {11}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));

  // Vector float type doesn't exist.
  ASSERT_FALSE(TransformationEquationInstruction(16, spv::Op::OpConvertUToF,
                                                 {14}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
  ASSERT_FALSE(TransformationEquationInstruction(16, spv::Op::OpConvertSToF,
                                                 {15}, return_instruction)
                   .IsApplicable(context.get(), transformation_context));
}

TEST(TransformationEquationInstructionTest, HandlesIrrelevantIds) {
  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
         %30 = OpTypeVector %6 3
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %31 = OpConstantComposite %30 %15 %16 %15
         %33 = OpTypeBool
         %32 = OpConstantTrue %33
         %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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);
  auto return_instruction = MakeInstructionDescriptor(13, spv::Op::OpReturn, 0);

  // Applicable.
  TransformationEquationInstruction transformation(
      14, spv::Op::OpIAdd, {15, 16}, return_instruction);
  ASSERT_TRUE(
      transformation.IsApplicable(context.get(), transformation_context));

  // Handles irrelevant ids.
  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(16);
  ASSERT_FALSE(
      transformation.IsApplicable(context.get(), transformation_context));
  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(15);
  ASSERT_FALSE(
      transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationEquationInstructionTest, HandlesDeadBlock) {
  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
         %30 = OpTypeVector %6 3
         %15 = OpConstant %6 24
         %16 = OpConstant %6 37
         %31 = OpConstantComposite %30 %15 %16 %15
         %33 = OpTypeBool
         %32 = OpConstantTrue %33
         %12 = OpFunction %2 None %3
         %13 = OpLabel
               OpSelectionMerge %40 None
               OpBranchConditional %32 %40 %41
         %41 = OpLabel
               OpBranch %40
         %40 = 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));
  TransformationContext transformation_context(
      MakeUnique<FactManager>(context.get()), validator_options);

  transformation_context.GetFactManager()->AddFactBlockIsDead(41);

  TransformationEquationInstruction transformation1(
      14, spv::Op::OpIAdd, {15, 16},
      MakeInstructionDescriptor(13, spv::Op::OpSelectionMerge, 0));
  // No synonym is created since block is dead.
  TransformationEquationInstruction transformation2(
      100, spv::Op::OpISub, {14, 16},
      MakeInstructionDescriptor(41, spv::Op::OpBranch, 0));
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);
  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
      MakeDataDescriptor(100, {}), MakeDataDescriptor(15, {})));
}

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