spirv-fuzz: Add fuzzer pass to change selection controls (#2944)

A new pass that allows the fuzzer to change the 'selection control'
operand of OpSelectionControl instructions.

Fixes #2937.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index b21d210..5ec62cd 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -38,6 +38,7 @@
         fuzzer_pass_add_dead_breaks.h
         fuzzer_pass_add_dead_continues.h
         fuzzer_pass_add_useful_constructs.h
+        fuzzer_pass_adjust_selection_controls.h
         fuzzer_pass_apply_id_synonyms.h
         fuzzer_pass_copy_objects.h
         fuzzer_pass_obfuscate_constants.h
@@ -64,6 +65,7 @@
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_id_with_synonym.h
+        transformation_set_selection_control.h
         transformation_split_block.h
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
@@ -77,6 +79,7 @@
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_dead_continues.cpp
         fuzzer_pass_add_useful_constructs.cpp
+        fuzzer_pass_adjust_selection_controls.cpp
         fuzzer_pass_apply_id_synonyms.cpp
         fuzzer_pass_copy_objects.cpp
         fuzzer_pass_obfuscate_constants.cpp
@@ -102,6 +105,7 @@
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_id_with_synonym.cpp
+        transformation_set_selection_control.cpp
         transformation_split_block.cpp
         uniform_buffer_element_descriptor.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 8e36a32..e28ecd7 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -23,6 +23,7 @@
 #include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
 #include "source/fuzz/fuzzer_pass_add_dead_continues.h"
 #include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
 #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
@@ -162,6 +163,15 @@
     passes[fuzzer_context.RandomIndex(passes)]->Apply();
   }
 
+  // Now apply some passes that it does not make sense to apply repeatedly,
+  // as they do not unlock other passes.
+  if (fuzzer_context.ChooseEven()) {
+    FuzzerPassAdjustSelectionControls(ir_context.get(), &fact_manager,
+                                      &fuzzer_context,
+                                      transformation_sequence_out)
+        .Apply();
+  }
+
   // Encode the module as a binary.
   ir_context->module()->ToBinary(binary_out, false);
 
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 69c6e56..5a8f394 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -25,6 +25,8 @@
 
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
+                                                                          90};
 const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
@@ -53,6 +55,8 @@
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
   chance_of_adding_dead_continue_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+  chance_of_adjusting_selection_control_ =
+      ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
   chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
   chance_of_moving_block_down_ =
       ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index a1ad66f..f50098e 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -62,6 +62,9 @@
   uint32_t GetChanceOfAddingDeadContinue() {
     return chance_of_adding_dead_continue_;
   }
+  uint32_t GetChanceOfAdjustingSelectionControl() {
+    return chance_of_adjusting_selection_control_;
+  }
   uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
   uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
   uint32_t GetChanceOfObfuscatingConstant() {
@@ -88,6 +91,7 @@
   // Keep them in alphabetical order.
   uint32_t chance_of_adding_dead_break_;
   uint32_t chance_of_adding_dead_continue_;
+  uint32_t chance_of_adjusting_selection_control_;
   uint32_t chance_of_copying_object_;
   uint32_t chance_of_moving_block_down_;
   uint32_t chance_of_obfuscating_constant_;
diff --git a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
index 6ac4ae9..489a084 100644
--- a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
+++ b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
@@ -24,8 +24,6 @@
 namespace spvtools {
 namespace fuzz {
 
-using opt::IRContext;
-
 FuzzerPassAddUsefulConstructs::FuzzerPassAddUsefulConstructs(
     opt::IRContext* ir_context, FactManager* fact_manager,
     FuzzerContext* fuzzer_context,
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp b/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
new file mode 100644
index 0000000..442fc49
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
@@ -0,0 +1,76 @@
+// 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/fuzzer_pass_adjust_selection_controls.h"
+
+#include "source/fuzz/transformation_set_selection_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAdjustSelectionControls::FuzzerPassAdjustSelectionControls(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+
+FuzzerPassAdjustSelectionControls::~FuzzerPassAdjustSelectionControls() =
+    default;
+
+void FuzzerPassAdjustSelectionControls::Apply() {
+  // Consider every merge instruction in the module (via looking through all
+  // functions and blocks).
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      if (auto merge_inst = block.GetMergeInst()) {
+        // Ignore the instruction if it is not a selection merge.
+        if (merge_inst->opcode() != SpvOpSelectionMerge) {
+          continue;
+        }
+
+        // Choose randomly whether to change the selection control for this
+        // instruction.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAdjustingSelectionControl())) {
+          continue;
+        }
+
+        // The choices to change the selection control to are the set of valid
+        // controls, minus the current control.
+        std::vector<uint32_t> choices;
+        for (auto control :
+             {SpvSelectionControlMaskNone, SpvSelectionControlFlattenMask,
+              SpvSelectionControlDontFlattenMask}) {
+          if (control == merge_inst->GetSingleWordOperand(1)) {
+            continue;
+          }
+          choices.push_back(control);
+        }
+
+        // Apply the transformation and add it to the output transformation
+        // sequence.
+        TransformationSetSelectionControl transformation(
+            block.id(), choices[GetFuzzerContext()->RandomIndex(choices)]);
+        assert(transformation.IsApplicable(GetIRContext(), *GetFactManager()) &&
+               "Transformation should be applicable by construction.");
+        transformation.Apply(GetIRContext(), GetFactManager());
+        *GetTransformations()->add_transformation() =
+            transformation.ToMessage();
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.h b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
new file mode 100644
index 0000000..b5b255c
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that adjusts the selection controls on OpSelectionMerge instructions.
+class FuzzerPassAdjustSelectionControls : public FuzzerPass {
+ public:
+  FuzzerPassAdjustSelectionControls(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAdjustSelectionControls() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 64ccd8e..d1c6e73 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -173,6 +173,7 @@
     TransformationAddDeadContinue add_dead_continue = 12;
     TransformationCopyObject copy_object = 13;
     TransformationReplaceIdWithSynonym replace_id_with_synonym = 14;
+    TransformationSetSelectionControl set_selection_control = 15;
     // Add additional option using the next available number.
   }
 }
@@ -384,6 +385,22 @@
   uint32 fresh_id_for_temporary = 3;
 }
 
+message TransformationSetSelectionControl {
+
+  // A transformation that sets the selection control operand of an
+  // OpSelectionMerge instruction.
+
+  // The id of a basic block that should contain OpSelectionMerge.
+  uint32 block_id = 1;
+
+  // The value to which the 'selection control' operand should be set.
+  // Although technically 'selection control' is a literal mask that can be
+  // some combination of 'None', 'Flatten' and 'DontFlatten', the combination
+  // 'Flatten | DontFlatten' does not make sense.
+  uint32 selection_control = 2;
+
+}
+
 message TransformationSplitBlock {
 
   // A transformation that splits a basic block into two basic blocks
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 65966f3..296f71f 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,6 +30,7 @@
 #include "transformation_replace_boolean_constant_with_constant_binary.h"
 #include "transformation_replace_constant_with_uniform.h"
 #include "transformation_replace_id_with_synonym.h"
+#include "transformation_set_selection_control.h"
 #include "transformation_split_block.h"
 
 namespace spvtools {
@@ -76,6 +77,9 @@
     case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
       return MakeUnique<TransformationReplaceIdWithSynonym>(
           message.replace_id_with_synonym());
+    case protobufs::Transformation::TransformationCase::kSetSelectionControl:
+      return MakeUnique<TransformationSetSelectionControl>(
+          message.set_selection_control());
     case protobufs::Transformation::TransformationCase::kSplitBlock:
       return MakeUnique<TransformationSplitBlock>(message.split_block());
     case protobufs::Transformation::TRANSFORMATION_NOT_SET:
diff --git a/source/fuzz/transformation_set_selection_control.cpp b/source/fuzz/transformation_set_selection_control.cpp
new file mode 100644
index 0000000..ebabdef
--- /dev/null
+++ b/source/fuzz/transformation_set_selection_control.cpp
@@ -0,0 +1,60 @@
+// 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_set_selection_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSetSelectionControl::TransformationSetSelectionControl(
+    const spvtools::fuzz::protobufs::TransformationSetSelectionControl& message)
+    : message_(message) {}
+
+TransformationSetSelectionControl::TransformationSetSelectionControl(
+    uint32_t block_id, uint32_t selection_control) {
+  message_.set_block_id(block_id);
+  message_.set_selection_control(selection_control);
+}
+
+bool TransformationSetSelectionControl::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  assert((message_.selection_control() == SpvSelectionControlMaskNone ||
+          message_.selection_control() == SpvSelectionControlFlattenMask ||
+          message_.selection_control() == SpvSelectionControlDontFlattenMask) &&
+         "Selection control should never be set to something other than "
+         "'None', 'Flatten' or 'DontFlatten'");
+  if (auto block = context->get_instr_block(message_.block_id())) {
+    if (auto merge_inst = block->GetMergeInst()) {
+      return merge_inst->opcode() == SpvOpSelectionMerge;
+    }
+  }
+  // Either the block did not exit, or did not end with OpSelectionMerge.
+  return false;
+}
+
+void TransformationSetSelectionControl::Apply(opt::IRContext* context,
+                                              FactManager* /*unused*/) const {
+  context->get_instr_block(message_.block_id())
+      ->GetMergeInst()
+      ->SetInOperand(1, {message_.selection_control()});
+}
+
+protobufs::Transformation TransformationSetSelectionControl::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_set_selection_control() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_set_selection_control.h b/source/fuzz/transformation_set_selection_control.h
new file mode 100644
index 0000000..19e0c3c
--- /dev/null
+++ b/source/fuzz/transformation_set_selection_control.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationSetSelectionControl : public Transformation {
+ public:
+  explicit TransformationSetSelectionControl(
+      const protobufs::TransformationSetSelectionControl& message);
+
+  TransformationSetSelectionControl(uint32_t block_id,
+                                    uint32_t selection_control);
+
+  // - |message_.block_id| must be a block containing an OpSelectionMerge
+  //   instruction.
+  // - |message_.selection_control| must be one of None, Flatten or
+  //   DontFlatten.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - The selection control operand of the OpSelectionMergeInstruction in
+  //   |message_.block_id| is overwritten with |message_.selection_control|.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationSetSelectionControl message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SET_SELECTION_CONTROL_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 1d9a8fd..f89d990 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -33,6 +33,7 @@
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
           transformation_replace_id_with_synonym_test.cpp
+          transformation_set_selection_control_test.cpp
           transformation_split_block_test.cpp
           uniform_buffer_element_descriptor_test.cpp)
 
diff --git a/test/fuzz/transformation_set_selection_control_test.cpp b/test/fuzz/transformation_set_selection_control_test.cpp
new file mode 100644
index 0000000..9696417
--- /dev/null
+++ b/test/fuzz/transformation_set_selection_control_test.cpp
@@ -0,0 +1,219 @@
+// 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_set_selection_control.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSetSelectionControlTest, VariousScenarios) {
+  // This is a simple transformation; this test captures the important things
+  // to check for.
+
+  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 10
+         %17 = OpTypeBool
+         %20 = OpConstant %6 3
+         %25 = OpConstant %6 1
+         %28 = OpConstant %6 2
+         %38 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               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
+         %19 = OpLoad %6 %8
+         %21 = OpSGreaterThan %17 %19 %20
+               OpSelectionMerge %23 Flatten
+               OpBranchConditional %21 %22 %23
+         %22 = OpLabel
+         %24 = OpLoad %6 %8
+         %26 = OpIAdd %6 %24 %25
+               OpStore %8 %26
+               OpBranch %23
+         %23 = OpLabel
+         %27 = OpLoad %6 %8
+         %29 = OpSLessThan %17 %27 %28
+               OpSelectionMerge %31 DontFlatten
+               OpBranchConditional %29 %30 %31
+         %30 = OpLabel
+         %32 = OpLoad %6 %8
+         %33 = OpISub %6 %32 %25
+               OpStore %8 %33
+               OpBranch %31
+         %31 = OpLabel
+         %34 = OpLoad %6 %8
+               OpSelectionMerge %37 None
+               OpSwitch %34 %36 0 %35
+         %36 = OpLabel
+               OpBranch %37
+         %35 = OpLabel
+         %39 = OpLoad %6 %8
+         %40 = OpIAdd %6 %39 %38
+               OpStore %8 %40
+               OpBranch %36
+         %37 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %43 = OpLoad %6 %8
+         %44 = OpIAdd %6 %43 %25
+               OpStore %8 %44
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // %44 is not a block
+  ASSERT_FALSE(
+      TransformationSetSelectionControl(44, SpvSelectionControlFlattenMask)
+          .IsApplicable(context.get(), fact_manager));
+  // %13 does not end with OpSelectionMerge
+  ASSERT_FALSE(
+      TransformationSetSelectionControl(13, SpvSelectionControlMaskNone)
+          .IsApplicable(context.get(), fact_manager));
+  // %10 ends in OpLoopMerge, not OpSelectionMerge
+  ASSERT_FALSE(
+      TransformationSetSelectionControl(10, SpvSelectionControlMaskNone)
+          .IsApplicable(context.get(), fact_manager));
+
+  TransformationSetSelectionControl transformation1(
+      11, SpvSelectionControlDontFlattenMask);
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+
+  TransformationSetSelectionControl transformation2(
+      23, SpvSelectionControlFlattenMask);
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+
+  TransformationSetSelectionControl transformation3(
+      31, SpvSelectionControlMaskNone);
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+
+  TransformationSetSelectionControl transformation4(
+      31, SpvSelectionControlFlattenMask);
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+
+  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 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 10
+         %17 = OpTypeBool
+         %20 = OpConstant %6 3
+         %25 = OpConstant %6 1
+         %28 = OpConstant %6 2
+         %38 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               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
+         %19 = OpLoad %6 %8
+         %21 = OpSGreaterThan %17 %19 %20
+               OpSelectionMerge %23 DontFlatten
+               OpBranchConditional %21 %22 %23
+         %22 = OpLabel
+         %24 = OpLoad %6 %8
+         %26 = OpIAdd %6 %24 %25
+               OpStore %8 %26
+               OpBranch %23
+         %23 = OpLabel
+         %27 = OpLoad %6 %8
+         %29 = OpSLessThan %17 %27 %28
+               OpSelectionMerge %31 Flatten
+               OpBranchConditional %29 %30 %31
+         %30 = OpLabel
+         %32 = OpLoad %6 %8
+         %33 = OpISub %6 %32 %25
+               OpStore %8 %33
+               OpBranch %31
+         %31 = OpLabel
+         %34 = OpLoad %6 %8
+               OpSelectionMerge %37 Flatten
+               OpSwitch %34 %36 0 %35
+         %36 = OpLabel
+               OpBranch %37
+         %35 = OpLabel
+         %39 = OpLoad %6 %8
+         %40 = OpIAdd %6 %39 %38
+               OpStore %8 %40
+               OpBranch %36
+         %37 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %43 = OpLoad %6 %8
+         %44 = OpIAdd %6 %43 %25
+               OpStore %8 %44
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools