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

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

namespace spvtools {
namespace fuzz {
namespace {

TEST(TransformationAddDeadContinueTest, SimpleExample) {
  // For a simple loop, checks that some dead continue scenarios are possible,
  // checks that some invalid scenarios are indeed not allowed, and then applies
  // a transformation.

  // The SPIR-V for this test is adapted from the following GLSL, by separating
  // some assignments into their own basic blocks, and adding constants for true
  // and false:
  //
  // void main() {
  //   int x = 0;
  //   for (int i = 0; i < 10; i++) {
  //     x = x + i;
  //     x = x + i;
  //   }
  // }

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %8 "x"
               OpName %10 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 0
         %17 = OpConstant %6 10
         %18 = OpTypeBool
         %41 = OpConstantTrue %18
         %42 = OpConstantFalse %18
         %27 = OpConstant %6 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %10 = OpVariable %7 Function
               OpStore %8 %9
               OpStore %10 %9
               OpBranch %11
         %11 = OpLabel
               OpLoopMerge %13 %14 None
               OpBranch %15
         %15 = OpLabel
         %16 = OpLoad %6 %10
         %19 = OpSLessThan %18 %16 %17
               OpBranchConditional %19 %12 %13
         %12 = OpLabel
         %20 = OpLoad %6 %8
         %21 = OpLoad %6 %10
         %22 = OpIAdd %6 %20 %21
               OpStore %8 %22
               OpBranch %40
         %40 = OpLabel
         %23 = OpLoad %6 %8
         %24 = OpLoad %6 %10
         %25 = OpIAdd %6 %23 %24
               OpStore %8 %25
               OpBranch %14
         %14 = OpLabel
         %26 = OpLoad %6 %10
         %28 = OpIAdd %6 %26 %27
               OpStore %10 %28
               OpBranch %11
         %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);
  // These are all possibilities.
  ASSERT_TRUE(TransformationAddDeadContinue(11, true, {})
                  .IsApplicable(context.get(), transformation_context));
  ASSERT_TRUE(TransformationAddDeadContinue(11, false, {})
                  .IsApplicable(context.get(), transformation_context));
  ASSERT_TRUE(TransformationAddDeadContinue(12, true, {})
                  .IsApplicable(context.get(), transformation_context));
  ASSERT_TRUE(TransformationAddDeadContinue(12, false, {})
                  .IsApplicable(context.get(), transformation_context));
  ASSERT_TRUE(TransformationAddDeadContinue(40, true, {})
                  .IsApplicable(context.get(), transformation_context));
  ASSERT_TRUE(TransformationAddDeadContinue(40, false, {})
                  .IsApplicable(context.get(), transformation_context));

  // Inapplicable: 100 is not a block id.
  ASSERT_FALSE(TransformationAddDeadContinue(100, true, {})
                   .IsApplicable(context.get(), transformation_context));

  // Inapplicable: 10 is not in a loop.
  ASSERT_FALSE(TransformationAddDeadContinue(10, true, {})
                   .IsApplicable(context.get(), transformation_context));

  // Inapplicable: 15 does not branch unconditionally to a single successor.
  ASSERT_FALSE(TransformationAddDeadContinue(15, true, {})
                   .IsApplicable(context.get(), transformation_context));

  // Inapplicable: 13 is not in a loop and has no successor.
  ASSERT_FALSE(TransformationAddDeadContinue(13, true, {})
                   .IsApplicable(context.get(), transformation_context));

  // Inapplicable: 14 is the loop continue target, so it's not OK to jump to
  // the loop continue from there.
  ASSERT_FALSE(TransformationAddDeadContinue(14, false, {})
                   .IsApplicable(context.get(), transformation_context));

  // These are the transformations we will apply.
  auto transformation1 = TransformationAddDeadContinue(11, true, {});
  auto transformation2 = TransformationAddDeadContinue(12, false, {});
  auto transformation3 = TransformationAddDeadContinue(40, true, {});

  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);
  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                               kConsoleMessageConsumer));

  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(
      transformation3.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation3, 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 %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %8 "x"
               OpName %10 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 0
         %17 = OpConstant %6 10
         %18 = OpTypeBool
         %41 = OpConstantTrue %18
         %42 = OpConstantFalse %18
         %27 = OpConstant %6 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %10 = OpVariable %7 Function
               OpStore %8 %9
               OpStore %10 %9
               OpBranch %11
         %11 = OpLabel
               OpLoopMerge %13 %14 None
               OpBranchConditional %41 %15 %14
         %15 = OpLabel
         %16 = OpLoad %6 %10
         %19 = OpSLessThan %18 %16 %17
               OpBranchConditional %19 %12 %13
         %12 = OpLabel
         %20 = OpLoad %6 %8
         %21 = OpLoad %6 %10
         %22 = OpIAdd %6 %20 %21
               OpStore %8 %22
               OpBranchConditional %42 %14 %40
         %40 = OpLabel
         %23 = OpLoad %6 %8
         %24 = OpLoad %6 %10
         %25 = OpIAdd %6 %23 %24
               OpStore %8 %25
               OpBranchConditional %41 %14 %14
         %14 = OpLabel
         %26 = OpLoad %6 %10
         %28 = OpIAdd %6 %26 %27
               OpStore %10 %28
               OpBranch %11
         %13 = OpLabel
               OpReturn
               OpFunctionEnd
  )";

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

TEST(TransformationAddDeadContinueTest, LoopNest) {
  // Checks some allowed and disallowed scenarios for a nest of loops, including
  // continuing a loop from an if or switch.

  // The SPIR-V for this test is adapted from the following GLSL:
  //
  // void main() {
  //   int x, y;
  //   do {
  //     x++;
  //     for (int j = 0; j < 100; j++) {
  //       y++;
  //       if (x == y) {
  //         x++;
  //         if (x == 2) {
  //           y++;
  //         }
  //         switch (x) {
  //           case 0:
  //             x = 2;
  //           default:
  //             break;
  //         }
  //       }
  //     }
  //   } while (x > y);
  //
  //   for (int i = 0; i < 100; i++) {
  //     x++;
  //   }
  // }

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %12 "x"
               OpName %16 "j"
               OpName %27 "y"
               OpName %55 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeInt 32 1
         %11 = OpTypePointer Function %10
         %14 = OpConstant %10 1
         %17 = OpConstant %10 0
         %24 = OpConstant %10 100
         %25 = OpTypeBool
         %38 = OpConstant %10 2
         %67 = OpConstantTrue %25
         %68 = OpConstantFalse %25
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %12 = OpVariable %11 Function
         %16 = OpVariable %11 Function
         %27 = OpVariable %11 Function
         %55 = OpVariable %11 Function
               OpBranch %6
          %6 = OpLabel
               OpLoopMerge %8 %9 None
               OpBranch %7
          %7 = OpLabel
         %13 = OpLoad %10 %12
         %15 = OpIAdd %10 %13 %14
               OpStore %12 %15
               OpStore %16 %17
               OpBranch %18
         %18 = OpLabel
               OpLoopMerge %20 %21 None
               OpBranch %22
         %22 = OpLabel
         %23 = OpLoad %10 %16
         %26 = OpSLessThan %25 %23 %24
               OpBranchConditional %26 %19 %20
         %19 = OpLabel
         %28 = OpLoad %10 %27
         %29 = OpIAdd %10 %28 %14
               OpStore %27 %29
         %30 = OpLoad %10 %12
         %31 = OpLoad %10 %27
         %32 = OpIEqual %25 %30 %31
               OpSelectionMerge %34 None
               OpBranchConditional %32 %33 %34
         %33 = OpLabel
         %35 = OpLoad %10 %12
         %36 = OpIAdd %10 %35 %14
               OpStore %12 %36
         %37 = OpLoad %10 %12
         %39 = OpIEqual %25 %37 %38
               OpSelectionMerge %41 None
               OpBranchConditional %39 %40 %41
         %40 = OpLabel
         %42 = OpLoad %10 %27
         %43 = OpIAdd %10 %42 %14
               OpStore %27 %43
               OpBranch %41
         %41 = OpLabel
         %44 = OpLoad %10 %12
               OpSelectionMerge %47 None
               OpSwitch %44 %46 0 %45
         %46 = OpLabel
               OpBranch %47
         %45 = OpLabel
               OpStore %12 %38
               OpBranch %46
         %47 = OpLabel
               OpBranch %34
         %34 = OpLabel
               OpBranch %21
         %21 = OpLabel
         %50 = OpLoad %10 %16
         %51 = OpIAdd %10 %50 %14
               OpStore %16 %51
               OpBranch %18
         %20 = OpLabel
               OpBranch %9
          %9 = OpLabel
         %52 = OpLoad %10 %12
         %53 = OpLoad %10 %27
         %54 = OpSGreaterThan %25 %52 %53
               OpBranchConditional %54 %6 %8
          %8 = OpLabel
               OpStore %55 %17
               OpBranch %56
         %56 = OpLabel
               OpLoopMerge %58 %59 None
               OpBranch %60
         %60 = OpLabel
         %61 = OpLoad %10 %55
         %62 = OpSLessThan %25 %61 %24
               OpBranchConditional %62 %57 %58
         %57 = OpLabel
         %63 = OpLoad %10 %12
         %64 = OpIAdd %10 %63 %14
               OpStore %12 %64
               OpBranch %59
         %59 = OpLabel
         %65 = OpLoad %10 %55
         %66 = OpIAdd %10 %65 %14
               OpStore %55 %66
               OpBranch %56
         %58 = 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);
  std::vector<uint32_t> good = {6, 7, 18, 20, 34, 40, 45, 46, 47, 56, 57};
  std::vector<uint32_t> bad = {5, 8, 9, 19, 21, 22, 33, 41, 58, 59, 60};

  for (uint32_t from_block : bad) {
    ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
                     .IsApplicable(context.get(), transformation_context));
  }
  for (uint32_t from_block : good) {
    const TransformationAddDeadContinue transformation(from_block, true, {});
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
    ASSERT_FALSE(
        transformation.IsApplicable(context.get(), transformation_context));
  }

  std::string after_transformation = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %12 "x"
               OpName %16 "j"
               OpName %27 "y"
               OpName %55 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeInt 32 1
         %11 = OpTypePointer Function %10
         %14 = OpConstant %10 1
         %17 = OpConstant %10 0
         %24 = OpConstant %10 100
         %25 = OpTypeBool
         %38 = OpConstant %10 2
         %67 = OpConstantTrue %25
         %68 = OpConstantFalse %25
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %12 = OpVariable %11 Function
         %16 = OpVariable %11 Function
         %27 = OpVariable %11 Function
         %55 = OpVariable %11 Function
               OpBranch %6
          %6 = OpLabel
               OpLoopMerge %8 %9 None
               OpBranchConditional %67 %7 %9
          %7 = OpLabel
         %13 = OpLoad %10 %12
         %15 = OpIAdd %10 %13 %14
               OpStore %12 %15
               OpStore %16 %17
               OpBranchConditional %67 %18 %9
         %18 = OpLabel
               OpLoopMerge %20 %21 None
               OpBranchConditional %67 %22 %21
         %22 = OpLabel
         %23 = OpLoad %10 %16
         %26 = OpSLessThan %25 %23 %24
               OpBranchConditional %26 %19 %20
         %19 = OpLabel
         %28 = OpLoad %10 %27
         %29 = OpIAdd %10 %28 %14
               OpStore %27 %29
         %30 = OpLoad %10 %12
         %31 = OpLoad %10 %27
         %32 = OpIEqual %25 %30 %31
               OpSelectionMerge %34 None
               OpBranchConditional %32 %33 %34
         %33 = OpLabel
         %35 = OpLoad %10 %12
         %36 = OpIAdd %10 %35 %14
               OpStore %12 %36
         %37 = OpLoad %10 %12
         %39 = OpIEqual %25 %37 %38
               OpSelectionMerge %41 None
               OpBranchConditional %39 %40 %41
         %40 = OpLabel
         %42 = OpLoad %10 %27
         %43 = OpIAdd %10 %42 %14
               OpStore %27 %43
               OpBranchConditional %67 %41 %21
         %41 = OpLabel
         %44 = OpLoad %10 %12
               OpSelectionMerge %47 None
               OpSwitch %44 %46 0 %45
         %46 = OpLabel
               OpBranchConditional %67 %47 %21
         %45 = OpLabel
               OpStore %12 %38
               OpBranchConditional %67 %46 %21
         %47 = OpLabel
               OpBranchConditional %67 %34 %21
         %34 = OpLabel
               OpBranchConditional %67 %21 %21
         %21 = OpLabel
         %50 = OpLoad %10 %16
         %51 = OpIAdd %10 %50 %14
               OpStore %16 %51
               OpBranch %18
         %20 = OpLabel
               OpBranchConditional %67 %9 %9
          %9 = OpLabel
         %52 = OpLoad %10 %12
         %53 = OpLoad %10 %27
         %54 = OpSGreaterThan %25 %52 %53
               OpBranchConditional %54 %6 %8
          %8 = OpLabel
               OpStore %55 %17
               OpBranch %56
         %56 = OpLabel
               OpLoopMerge %58 %59 None
               OpBranchConditional %67 %60 %59
         %60 = OpLabel
         %61 = OpLoad %10 %55
         %62 = OpSLessThan %25 %61 %24
               OpBranchConditional %62 %57 %58
         %57 = OpLabel
         %63 = OpLoad %10 %12
         %64 = OpIAdd %10 %63 %14
               OpStore %12 %64
               OpBranchConditional %67 %59 %59
         %59 = OpLabel
         %65 = OpLoad %10 %55
         %66 = OpIAdd %10 %65 %14
               OpStore %55 %66
               OpBranch %56
         %58 = OpLabel
               OpReturn
               OpFunctionEnd
  )";

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

TEST(TransformationAddDeadConditionalTest, LoopInContinueConstruct) {
  // Considers some scenarios where there is a loop in a loop's continue
  // construct.

  // The SPIR-V for this test is adapted from the following GLSL, with inlining
  // applied so that the loop from foo is in the main loop's continue construct:
  //
  // int foo() {
  //   int result = 0;
  //   for (int j = 0; j < 10; j++) {
  //     result++;
  //   }
  //   return result;
  // }
  //
  // void main() {
  //   for (int i = 0; i < 100; i += foo()) {
  //   }
  // }

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %31 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypeFunction %6
         %10 = OpTypePointer Function %6
         %12 = OpConstant %6 0
         %20 = OpConstant %6 10
         %21 = OpTypeBool
        %100 = OpConstantFalse %21
         %24 = OpConstant %6 1
         %38 = OpConstant %6 100
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %43 = OpVariable %10 Function
         %44 = OpVariable %10 Function
         %45 = OpVariable %10 Function
         %31 = OpVariable %10 Function
               OpStore %31 %12
               OpBranch %32
         %32 = OpLabel
               OpLoopMerge %34 %35 None
               OpBranch %36
         %36 = OpLabel
         %37 = OpLoad %6 %31
         %39 = OpSLessThan %21 %37 %38
               OpBranchConditional %39 %33 %34
         %33 = OpLabel
               OpBranch %35
         %35 = OpLabel
               OpStore %43 %12
               OpStore %44 %12
               OpBranch %46
         %46 = OpLabel
               OpLoopMerge %47 %48 None
               OpBranch %49
         %49 = OpLabel
         %50 = OpLoad %6 %44
         %51 = OpSLessThan %21 %50 %20
               OpBranchConditional %51 %52 %47
         %52 = OpLabel
         %53 = OpLoad %6 %43
               OpBranch %101
        %101 = OpLabel
         %54 = OpIAdd %6 %53 %24
               OpStore %43 %54
               OpBranch %48
         %48 = OpLabel
         %55 = OpLoad %6 %44
         %56 = OpIAdd %6 %55 %24
               OpStore %44 %56
               OpBranch %46
         %47 = OpLabel
         %57 = OpLoad %6 %43
               OpStore %45 %57
         %40 = OpLoad %6 %45
         %41 = OpLoad %6 %31
         %42 = OpIAdd %6 %41 %40
               OpStore %31 %42
               OpBranch %32
         %34 = 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);
  std::vector<uint32_t> good = {32, 33, 46, 52, 101};
  std::vector<uint32_t> bad = {5, 34, 36, 35, 47, 49, 48};

  for (uint32_t from_block : bad) {
    ASSERT_FALSE(TransformationAddDeadContinue(from_block, false, {})
                     .IsApplicable(context.get(), transformation_context));
  }
  for (uint32_t from_block : good) {
    const TransformationAddDeadContinue transformation(from_block, false, {});
    ASSERT_TRUE(
        transformation.IsApplicable(context.get(), transformation_context));
    ApplyAndCheckFreshIds(transformation, context.get(),
                          &transformation_context);
    ASSERT_FALSE(
        transformation.IsApplicable(context.get(), transformation_context));
  }

  std::string after_transformation = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %31 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypeFunction %6
         %10 = OpTypePointer Function %6
         %12 = OpConstant %6 0
         %20 = OpConstant %6 10
         %21 = OpTypeBool
        %100 = OpConstantFalse %21
         %24 = OpConstant %6 1
         %38 = OpConstant %6 100
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %43 = OpVariable %10 Function
         %44 = OpVariable %10 Function
         %45 = OpVariable %10 Function
         %31 = OpVariable %10 Function
               OpStore %31 %12
               OpBranch %32
         %32 = OpLabel
               OpLoopMerge %34 %35 None
               OpBranchConditional %100 %35 %36
         %36 = OpLabel
         %37 = OpLoad %6 %31
         %39 = OpSLessThan %21 %37 %38
               OpBranchConditional %39 %33 %34
         %33 = OpLabel
               OpBranchConditional %100 %35 %35
         %35 = OpLabel
               OpStore %43 %12
               OpStore %44 %12
               OpBranch %46
         %46 = OpLabel
               OpLoopMerge %47 %48 None
               OpBranchConditional %100 %48 %49
         %49 = OpLabel
         %50 = OpLoad %6 %44
         %51 = OpSLessThan %21 %50 %20
               OpBranchConditional %51 %52 %47
         %52 = OpLabel
         %53 = OpLoad %6 %43
               OpBranchConditional %100 %48 %101
        %101 = OpLabel
         %54 = OpIAdd %6 %53 %24
               OpStore %43 %54
               OpBranchConditional %100 %48 %48
         %48 = OpLabel
         %55 = OpLoad %6 %44
         %56 = OpIAdd %6 %55 %24
               OpStore %44 %56
               OpBranch %46
         %47 = OpLabel
         %57 = OpLoad %6 %43
               OpStore %45 %57
         %40 = OpLoad %6 %45
         %41 = OpLoad %6 %31
         %42 = OpIAdd %6 %41 %40
               OpStore %31 %42
               OpBranch %32
         %34 = OpLabel
               OpReturn
               OpFunctionEnd
  )";

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

TEST(TransformationAddDeadContinueTest, PhiInstructions) {
  // Checks that the transformation works in the presence of phi instructions.

  // The SPIR-V for this test is adapted from the following GLSL, with a bit of
  // extra and artificial work to get some interesting uses of OpPhi:
  //
  // void main() {
  //   int x; int y;
  //   float f;
  //   x = 2;
  //   f = 3.0;
  //   if (x > y) {
  //     x = 3;
  //     f = 4.0;
  //   } else {
  //     x = x + 2;
  //     f = f + 10.0;
  //   }
  //   while (x < y) {
  //     x = x + 1;
  //     f = f + 1.0;
  //   }
  //   y = x;
  //   f = f + 3.0;
  // }

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %8 "x"
               OpName %12 "f"
               OpName %15 "y"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 2
         %10 = OpTypeFloat 32
         %11 = OpTypePointer Function %10
         %13 = OpConstant %10 3
         %17 = OpTypeBool
         %80 = OpConstantTrue %17
         %21 = OpConstant %6 3
         %22 = OpConstant %10 4
         %27 = OpConstant %10 10
         %38 = OpConstant %6 1
         %41 = OpConstant %10 1
         %46 = OpUndef %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %12 = OpVariable %11 Function
         %15 = OpVariable %7 Function
               OpStore %8 %9
               OpStore %12 %13
         %18 = OpSGreaterThan %17 %9 %46
               OpSelectionMerge %20 None
               OpBranchConditional %18 %19 %23
         %19 = OpLabel
               OpStore %8 %21
               OpStore %12 %22
               OpBranch %20
         %23 = OpLabel
         %25 = OpIAdd %6 %9 %9
               OpStore %8 %25
               OpBranch %70
         %70 = OpLabel
         %28 = OpFAdd %10 %13 %27
               OpStore %12 %28
               OpBranch %20
         %20 = OpLabel
         %52 = OpPhi %10 %22 %19 %28 %70
         %48 = OpPhi %6 %21 %19 %25 %70
               OpBranch %29
         %29 = OpLabel
         %51 = OpPhi %10 %52 %20 %100 %32
         %47 = OpPhi %6 %48 %20 %101 %32
               OpLoopMerge %31 %32 None
               OpBranch %33
         %33 = OpLabel
         %36 = OpSLessThan %17 %47 %46
               OpBranchConditional %36 %30 %31
         %30 = OpLabel
         %39 = OpIAdd %6 %47 %38
               OpStore %8 %39
               OpBranch %75
         %75 = OpLabel
         %42 = OpFAdd %10 %51 %41
               OpStore %12 %42
               OpBranch %32
         %32 = OpLabel
        %100 = OpPhi %10 %42 %75
        %101 = OpPhi %6 %39 %75
               OpBranch %29
         %31 = OpLabel
         %71 = OpPhi %6 %47 %33
         %72 = OpPhi %10 %51 %33
               OpStore %15 %71
         %45 = OpFAdd %10 %72 %13
               OpStore %12 %45
               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);
  std::vector<uint32_t> bad = {5, 19, 20, 23, 31, 32, 33, 70};

  std::vector<uint32_t> good = {29, 30, 75};

  for (uint32_t from_block : bad) {
    ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {})
                     .IsApplicable(context.get(), transformation_context));
  }
  auto transformation1 = TransformationAddDeadContinue(29, true, {13, 21});
  ASSERT_TRUE(
      transformation1.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation1, context.get(),
                        &transformation_context);

  auto transformation2 = TransformationAddDeadContinue(30, true, {22, 46});
  ASSERT_TRUE(
      transformation2.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation2, context.get(),
                        &transformation_context);

  // 75 already has the continue block as a successor, so we should not provide
  // phi ids.
  auto transformationBad = TransformationAddDeadContinue(75, true, {27, 46});
  ASSERT_FALSE(
      transformationBad.IsApplicable(context.get(), transformation_context));

  auto transformation3 = TransformationAddDeadContinue(75, true, {});
  ASSERT_TRUE(
      transformation3.IsApplicable(context.get(), transformation_context));
  ApplyAndCheckFreshIds(transformation3, context.get(),
                        &transformation_context);

  std::string after_transformation = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %8 "x"
               OpName %12 "f"
               OpName %15 "y"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 2
         %10 = OpTypeFloat 32
         %11 = OpTypePointer Function %10
         %13 = OpConstant %10 3
         %17 = OpTypeBool
         %80 = OpConstantTrue %17
         %21 = OpConstant %6 3
         %22 = OpConstant %10 4
         %27 = OpConstant %10 10
         %38 = OpConstant %6 1
         %41 = OpConstant %10 1
         %46 = OpUndef %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %12 = OpVariable %11 Function
         %15 = OpVariable %7 Function
               OpStore %8 %9
               OpStore %12 %13
         %18 = OpSGreaterThan %17 %9 %46
               OpSelectionMerge %20 None
               OpBranchConditional %18 %19 %23
         %19 = OpLabel
               OpStore %8 %21
               OpStore %12 %22
               OpBranch %20
         %23 = OpLabel
         %25 = OpIAdd %6 %9 %9
               OpStore %8 %25
               OpBranch %70
         %70 = OpLabel
         %28 = OpFAdd %10 %13 %27
               OpStore %12 %28
               OpBranch %20
         %20 = OpLabel
         %52 = OpPhi %10 %22 %19 %28 %70
         %48 = OpPhi %6 %21 %19 %25 %70
               OpBranch %29
         %29 = OpLabel
         %51 = OpPhi %10 %52 %20 %100 %32
         %47 = OpPhi %6 %48 %20 %101 %32
               OpLoopMerge %31 %32 None
               OpBranchConditional %80 %33 %32
         %33 = OpLabel
         %36 = OpSLessThan %17 %47 %46
               OpBranchConditional %36 %30 %31
         %30 = OpLabel
         %39 = OpIAdd %6 %47 %38
               OpStore %8 %39
               OpBranchConditional %80 %75 %32
         %75 = OpLabel
         %42 = OpFAdd %10 %51 %41
               OpStore %12 %42
               OpBranchConditional %80 %32 %32
         %32 = OpLabel
        %100 = OpPhi %10 %42 %75 %13 %29 %22 %30
        %101 = OpPhi %6 %39 %75 %21 %29 %46 %30
               OpBranch %29
         %31 = OpLabel
         %71 = OpPhi %6 %47 %33
         %72 = OpPhi %10 %51 %33
               OpStore %15 %71
         %45 = OpFAdd %10 %72 %13
               OpStore %12 %45
               OpReturn
               OpFunctionEnd
  )";

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

TEST(TransformationAddDeadContinueTest, RespectDominanceRules1) {
  // Checks that a dead continue cannot be added if it would prevent a block
  // later in the loop from dominating the loop's continue construct, in the
  // case where said block defines and id that is used in the loop's continue
  // construct.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeBool
         %11 = OpConstantFalse %10
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %6
          %6 = OpLabel
               OpLoopMerge %8 %9 None
               OpBranch %7
          %7 = OpLabel
         %21 = OpCopyObject %10 %11
               OpBranch %9
          %9 = OpLabel
         %20 = OpPhi %10 %21 %7
               OpBranchConditional %11 %6 %8
          %8 = OpLabel
               OpBranch %12
         %12 = OpLabel
               OpLoopMerge %14 %15 None
               OpBranch %13
         %13 = OpLabel
               OpBranch %22
         %22 = OpLabel
         %23 = OpCopyObject %10 %11
               OpBranch %25
         %25 = OpLabel
               OpBranch %15
         %15 = OpLabel
         %26 = OpCopyObject %10 %23
               OpBranchConditional %11 %12 %14
         %14 = 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);
  // This transformation is not applicable because the dead continue from the
  // loop body prevents the definition of %23 later in the loop body from
  // dominating its use in the loop's continue target.
  auto bad_transformation = TransformationAddDeadContinue(13, false, {});
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));

  auto good_transformation_1 = TransformationAddDeadContinue(7, false, {});
  ASSERT_TRUE(good_transformation_1.IsApplicable(context.get(),
                                                 transformation_context));
  ApplyAndCheckFreshIds(good_transformation_1, context.get(),
                        &transformation_context);

  auto good_transformation_2 = TransformationAddDeadContinue(22, false, {});
  ASSERT_TRUE(good_transformation_2.IsApplicable(context.get(),
                                                 transformation_context));
  ApplyAndCheckFreshIds(good_transformation_2, context.get(),
                        &transformation_context);

  // This transformation is OK, because the definition of %21 in the loop body
  // is only used in an OpPhi in the loop's continue target.
  auto good_transformation_3 = TransformationAddDeadContinue(6, false, {11});
  ASSERT_TRUE(good_transformation_3.IsApplicable(context.get(),
                                                 transformation_context));
  ApplyAndCheckFreshIds(good_transformation_3, context.get(),
                        &transformation_context);

  std::string after_transformations = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeBool
         %11 = OpConstantFalse %10
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %6
          %6 = OpLabel
               OpLoopMerge %8 %9 None
               OpBranchConditional %11 %9 %7
          %7 = OpLabel
         %21 = OpCopyObject %10 %11
               OpBranchConditional %11 %9 %9
          %9 = OpLabel
         %20 = OpPhi %10 %21 %7 %11 %6
               OpBranchConditional %11 %6 %8
          %8 = OpLabel
               OpBranch %12
         %12 = OpLabel
               OpLoopMerge %14 %15 None
               OpBranch %13
         %13 = OpLabel
               OpBranch %22
         %22 = OpLabel
         %23 = OpCopyObject %10 %11
               OpBranchConditional %11 %15 %25
         %25 = OpLabel
               OpBranch %15
         %15 = OpLabel
         %26 = OpCopyObject %10 %23
               OpBranchConditional %11 %12 %14
         %14 = OpLabel
               OpReturn
               OpFunctionEnd
  )";

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

TEST(TransformationAddDeadContinueTest, RespectDominanceRules2) {
  // Checks that a dead continue cannot be added if it would lead to a use after
  // the loop failing to be dominated by its definition.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeBool
         %11 = OpConstantFalse %10
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %100
        %100 = OpLabel
               OpLoopMerge %101 %102 None
               OpBranch %103
        %103 = OpLabel
        %200 = OpCopyObject %10 %11
               OpBranch %104
        %104 = OpLabel
               OpBranch %102
        %102 = OpLabel
               OpBranchConditional %11 %100 %101
        %101 = OpLabel
        %201 = OpCopyObject %10 %200
               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);
  // This transformation would shortcut the part of the loop body that defines
  // an id used after the loop.
  auto bad_transformation = TransformationAddDeadContinue(100, false, {});
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, RespectDominanceRules3) {
  // Checks that a dead continue cannot be added if it would lead to a dominance
  // problem with an id used in an OpPhi after the loop.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeBool
         %11 = OpConstantFalse %10
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %100
        %100 = OpLabel
               OpLoopMerge %101 %102 None
               OpBranch %103
        %103 = OpLabel
        %200 = OpCopyObject %10 %11
               OpBranch %104
        %104 = OpLabel
               OpBranch %102
        %102 = OpLabel
               OpBranchConditional %11 %100 %101
        %101 = OpLabel
        %201 = OpPhi %10 %200 %102
               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);
  // This transformation would shortcut the part of the loop body that defines
  // an id used after the loop.
  auto bad_transformation = TransformationAddDeadContinue(100, false, {});
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, Miscellaneous1) {
  // A miscellaneous test that exposed a bug in spirv-fuzz.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main" %586 %623
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpMemberDecorate %34 0 Offset 0
               OpDecorate %34 Block
               OpDecorate %36 DescriptorSet 0
               OpDecorate %36 Binding 0
               OpDecorate %586 BuiltIn FragCoord
               OpMemberDecorate %591 0 Offset 0
               OpDecorate %591 Block
               OpDecorate %593 DescriptorSet 0
               OpDecorate %593 Binding 1
               OpDecorate %623 Location 0
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 0
         %16 = OpConstant %6 2
         %17 = OpTypeBool
         %27 = OpTypeFloat 32
         %28 = OpTypeVector %27 2
         %29 = OpTypeMatrix %28 2
         %30 = OpTypePointer Private %29
         %31 = OpVariable %30 Private
         %34 = OpTypeStruct %27
         %35 = OpTypePointer Uniform %34
         %36 = OpVariable %35 Uniform
         %37 = OpTypePointer Uniform %27
         %40 = OpTypePointer Private %27
         %43 = OpConstant %6 1
         %62 = OpConstant %6 3
         %64 = OpTypeVector %27 3
         %65 = OpTypeMatrix %64 2
         %66 = OpTypePointer Private %65
         %67 = OpVariable %66 Private
         %92 = OpConstant %6 4
         %94 = OpTypeVector %27 4
         %95 = OpTypeMatrix %94 2
         %96 = OpTypePointer Private %95
         %97 = OpVariable %96 Private
        %123 = OpTypeMatrix %28 3
        %124 = OpTypePointer Private %123
        %125 = OpVariable %124 Private
        %151 = OpTypeMatrix %64 3
        %152 = OpTypePointer Private %151
        %153 = OpVariable %152 Private
        %179 = OpTypeMatrix %94 3
        %180 = OpTypePointer Private %179
        %181 = OpVariable %180 Private
        %207 = OpTypeMatrix %28 4
        %208 = OpTypePointer Private %207
        %209 = OpVariable %208 Private
        %235 = OpTypeMatrix %64 4
        %236 = OpTypePointer Private %235
        %237 = OpVariable %236 Private
        %263 = OpTypeMatrix %94 4
        %264 = OpTypePointer Private %263
        %265 = OpVariable %264 Private
        %275 = OpTypeInt 32 0
        %276 = OpConstant %275 9
        %277 = OpTypeArray %27 %276
        %278 = OpTypePointer Function %277
        %280 = OpConstant %27 0
        %281 = OpTypePointer Function %27
        %311 = OpConstant %27 16
        %448 = OpConstant %6 5
        %482 = OpConstant %6 6
        %516 = OpConstant %6 7
        %550 = OpConstant %6 8
        %585 = OpTypePointer Input %94
        %586 = OpVariable %585 Input
        %587 = OpConstant %275 0
        %588 = OpTypePointer Input %27
        %591 = OpTypeStruct %28
        %592 = OpTypePointer Uniform %591
        %593 = OpVariable %592 Uniform
        %596 = OpConstant %27 3
        %601 = OpConstant %275 1
        %617 = OpConstant %6 9
        %622 = OpTypePointer Output %94
        %623 = OpVariable %622 Output
        %628 = OpConstant %27 1
        %634 = OpConstantComposite %94 %280 %280 %280 %628
        %635 = OpUndef %6
        %636 = OpUndef %17
        %637 = OpUndef %27
        %638 = OpUndef %64
        %639 = OpUndef %94
        %640 = OpConstantTrue %17
        %736 = OpConstantFalse %17
        %642 = OpVariable %37 Uniform
        %643 = OpVariable %40 Private
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %164
        %164 = OpLabel
               OpLoopMerge %166 %167 None
               OpBranch %165
        %165 = OpLabel
               OpBranch %172
        %172 = OpLabel
               OpSelectionMerge %174 None
               OpBranchConditional %640 %174 %174
        %174 = OpLabel
        %785 = OpCopyObject %6 %43
               OpBranch %167
        %167 = OpLabel
        %190 = OpIAdd %6 %9 %785
               OpBranchConditional %640 %164 %166
        %166 = OpLabel
               OpBranch %196
        %196 = OpLabel
               OpBranch %194
        %194 = 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);
  // This transformation would shortcut the part of the loop body that defines
  // an id used in the continue target.
  auto bad_transformation = TransformationAddDeadContinue(165, false, {});
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, Miscellaneous2) {
  // A miscellaneous test that exposed a bug in spirv-fuzz.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %51 = OpTypeBool
        %395 = OpConstantTrue %51
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %389
        %389 = OpLabel
               OpLoopMerge %388 %391 None
               OpBranch %339
        %339 = OpLabel
               OpSelectionMerge %396 None
               OpBranchConditional %395 %388 %396
        %396 = OpLabel
               OpBranch %1552
       %1552 = OpLabel
               OpLoopMerge %1553 %1554 None
               OpBranch %1556
       %1556 = OpLabel
               OpLoopMerge %1557 %1570 None
               OpBranchConditional %395 %1562 %1557
       %1562 = OpLabel
               OpBranchConditional %395 %1571 %1570
       %1571 = OpLabel
               OpBranch %1557
       %1570 = OpLabel
               OpBranch %1556
       %1557 = OpLabel
               OpSelectionMerge %1586 None
               OpBranchConditional %395 %1553 %1586
       %1586 = OpLabel
               OpBranch %1553
       %1554 = OpLabel
               OpBranch %1552
       %1553 = OpLabel
               OpBranch %388
        %391 = OpLabel
               OpBranch %389
        %388 = 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);
  // This transformation would introduce a branch from a continue target to
  // itself.
  auto bad_transformation = TransformationAddDeadContinue(1554, true, {});
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, Miscellaneous3) {
  // A miscellaneous test that exposed a bug in spirv-fuzz.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %85 = OpTypeBool
        %434 = OpConstantFalse %85
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %234
        %234 = OpLabel
               OpLoopMerge %235 %236 None
               OpBranch %259
        %259 = OpLabel
               OpLoopMerge %260 %274 None
               OpBranchConditional %434 %265 %260
        %265 = OpLabel
               OpBranch %275
        %275 = OpLabel
               OpBranch %260
        %274 = OpLabel
               OpBranch %259
        %260 = OpLabel
               OpSelectionMerge %298 None
               OpBranchConditional %434 %299 %300
        %300 = OpLabel
               OpBranch %235
        %298 = OpLabel
               OpUnreachable
        %236 = OpLabel
               OpBranch %234
        %299 = OpLabel
               OpBranch %235
        %235 = 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 bad_transformation = TransformationAddDeadContinue(299, false, {});

  // The continue edge would connect %299 to the previously-unreachable %236,
  // making %299 dominate %236, and breaking the rule that block ordering must
  // respect dominance.
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, Miscellaneous4) {
  // A miscellaneous test that exposed a bug in spirv-fuzz.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpName %4 "main"
               OpName %8 "i"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 0
         %16 = OpConstant %6 100
         %17 = OpTypeBool
        %100 = OpConstantFalse %17
         %21 = OpConstant %6 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
               OpStore %8 %9
               OpBranch %10
         %13 = OpLabel
         %20 = OpLoad %6 %8
         %22 = OpIAdd %6 %20 %21
               OpStore %8 %22
               OpBranch %10
         %10 = OpLabel
               OpLoopMerge %12 %13 None
               OpBranch %14
         %14 = OpLabel
         %15 = OpLoad %6 %8
         %18 = OpSLessThan %17 %15 %16
               OpBranchConditional %18 %11 %12
         %11 = OpLabel
               OpBranch %12
         %12 = 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 bad_transformation = TransformationAddDeadContinue(10, false, {});

  // The continue edge would connect %10 to the previously-unreachable %13,
  // making %10 dominate %13, and breaking the rule that block ordering must
  // respect dominance.
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, Miscellaneous5) {
  // A miscellaneous test that exposed a bug in spirv-fuzz.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeBool
          %7 = OpTypePointer Function %6
          %9 = OpConstantTrue %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %98
         %98 = OpLabel
               OpLoopMerge %100 %101 None
               OpBranch %99
         %99 = OpLabel
               OpSelectionMerge %111 None
               OpBranchConditional %9 %110 %111
        %110 = OpLabel
               OpBranch %100
        %111 = OpLabel
        %200 = OpCopyObject %6 %9
               OpBranch %101
        %101 = OpLabel
        %201 = OpCopyObject %6 %200
               OpBranchConditional %9 %98 %100
        %100 = 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 bad_transformation = TransformationAddDeadContinue(110, true, {});

  // The continue edge would lead to the use of %200 in block %101 no longer
  // being dominated by its definition in block %111.
  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

TEST(TransformationAddDeadContinueTest, Miscellaneous6) {
  // A miscellaneous test that exposed a bug in spirv-fuzz.

  std::string shader = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeBool
          %9 = OpConstantTrue %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
               OpBranch %10
         %10 = OpLabel
               OpLoopMerge %13 %12 None
               OpBranch %11
         %11 = OpLabel
         %20 = OpCopyObject %6 %9
               OpBranch %12
         %12 = OpLabel
               OpBranchConditional %9 %10 %13
         %13 = OpLabel
         %21 = OpCopyObject %6 %20
               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 bad_transformation = TransformationAddDeadContinue(10, true, {});

  ASSERT_FALSE(
      bad_transformation.IsApplicable(context.get(), transformation_context));
}

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