spirv-fuzz: Add fuzzer pass to change loop controls (#2949)

A new pass that allows the fuzzer to change the 'loop control' operand
(and associated literal operands) of OpLoopMerge instructions.

Fixes #2938.
Fixes #2943.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index dbb4964..c711d05 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_loop_controls.h
         fuzzer_pass_adjust_selection_controls.h
         fuzzer_pass_apply_id_synonyms.h
         fuzzer_pass_construct_composites.h
@@ -67,6 +68,7 @@
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_id_with_synonym.h
+        transformation_set_loop_control.h
         transformation_set_selection_control.h
         transformation_split_block.h
         uniform_buffer_element_descriptor.h
@@ -81,6 +83,7 @@
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_dead_continues.cpp
         fuzzer_pass_add_useful_constructs.cpp
+        fuzzer_pass_adjust_loop_controls.cpp
         fuzzer_pass_adjust_selection_controls.cpp
         fuzzer_pass_apply_id_synonyms.cpp
         fuzzer_pass_construct_composites.cpp
@@ -109,6 +112,7 @@
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_id_with_synonym.cpp
+        transformation_set_loop_control.cpp
         transformation_set_selection_control.cpp
         transformation_split_block.cpp
         uniform_buffer_element_descriptor.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index fbeafb3..a034c0f 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_loop_controls.h"
 #include "source/fuzz/fuzzer_pass_adjust_selection_controls.h"
 #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
 #include "source/fuzz/fuzzer_pass_construct_composites.h"
@@ -170,6 +171,11 @@
   // Now apply some passes that it does not make sense to apply repeatedly,
   // as they do not unlock other passes.
   if (fuzzer_context.ChooseEven()) {
+    FuzzerPassAdjustLoopControls(ir_context.get(), &fact_manager,
+                                 &fuzzer_context, transformation_sequence_out)
+        .Apply();
+  }
+  if (fuzzer_context.ChooseEven()) {
     FuzzerPassAdjustSelectionControls(ir_context.get(), &fact_manager,
                                       &fuzzer_context,
                                       transformation_sequence_out)
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 2328621..5a49e5e 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -25,6 +25,7 @@
 
 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> kChanceOfAdjustingLoopControl = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingSelectionControl = {20,
                                                                           90};
 const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
@@ -34,6 +35,11 @@
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
 
+// Default limits for various quantities that are chosen during fuzzing.
+// Keep them in alphabetical order.
+const uint32_t kDefaultMaxLoopControlPartialCount = 100;
+const uint32_t kDefaultMaxLoopControlPeelCount = 100;
+
 // Default functions for controlling how deep to go during recursive
 // generation/transformation. Keep them in alphabetical order.
 
@@ -56,6 +62,8 @@
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
   chance_of_adding_dead_continue_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+  chance_of_adjusting_loop_control_ =
+      ChooseBetweenMinAndMax(kChanceOfAdjustingLoopControl);
   chance_of_adjusting_selection_control_ =
       ChooseBetweenMinAndMax(kChanceOfAdjustingSelectionControl);
   chance_of_constructing_composite_ =
@@ -68,6 +76,8 @@
   chance_of_replacing_id_with_synonym_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
   chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
+  max_loop_control_partial_count_ = kDefaultMaxLoopControlPartialCount;
+  max_loop_control_peel_count_ = kDefaultMaxLoopControlPeelCount;
 }
 
 FuzzerContext::~FuzzerContext() = default;
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index eabe346..5e1b1d2 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 GetChanceOfAdjustingLoopControl() {
+    return chance_of_adjusting_loop_control_;
+  }
   uint32_t GetChanceOfAdjustingSelectionControl() {
     return chance_of_adjusting_selection_control_;
   }
@@ -77,6 +80,12 @@
     return chance_of_replacing_id_with_synonym_;
   }
   uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
+  uint32_t GetRandomLoopControlPeelCount() {
+    return random_generator_->RandomUint32(max_loop_control_peel_count_);
+  }
+  uint32_t GetRandomLoopControlPartialCount() {
+    return random_generator_->RandomUint32(max_loop_control_partial_count_);
+  }
 
   // Functions to control how deeply to recurse.
   // Keep them in alphabetical order.
@@ -94,6 +103,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_loop_control_;
   uint32_t chance_of_adjusting_selection_control_;
   uint32_t chance_of_constructing_composite_;
   uint32_t chance_of_copying_object_;
@@ -102,6 +112,12 @@
   uint32_t chance_of_replacing_id_with_synonym_;
   uint32_t chance_of_splitting_block_;
 
+  // Limits associated with various quantities for which random values are
+  // chosen during fuzzing.
+  // Keep them in alphabetical order.
+  uint32_t max_loop_control_partial_count_;
+  uint32_t max_loop_control_peel_count_;
+
   // Functions to determine with what probability to go deeper when generating
   // or mutating constructs recursively.
   const std::function<bool(uint32_t, RandomGenerator*)>&
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
new file mode 100644
index 0000000..65b8d95
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
@@ -0,0 +1,121 @@
+// 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_loop_controls.h"
+
+#include "source/fuzz/transformation_set_loop_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+
+FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
+
+void FuzzerPassAdjustLoopControls::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 loop merge.
+        if (merge_inst->opcode() != SpvOpLoopMerge) {
+          continue;
+        }
+
+        // Decide randomly whether to adjust this loop merge.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAdjustingLoopControl())) {
+          continue;
+        }
+
+        uint32_t existing_mask = merge_inst->GetSingleWordOperand(
+            TransformationSetLoopControl::kLoopControlMaskInOperandIndex);
+
+        // First, set the new mask to one of None, Unroll or DontUnroll.
+        std::vector<uint32_t> basic_masks = {SpvLoopControlMaskNone,
+                                             SpvLoopControlUnrollMask,
+                                             SpvLoopControlDontUnrollMask};
+        uint32_t new_mask =
+            basic_masks[GetFuzzerContext()->RandomIndex(basic_masks)];
+
+        // For the loop controls that depend on guarantees about what the loop
+        // does, check which of these were present in the existing mask and
+        // randomly decide whether to keep them.  They are just hints, so
+        // removing them should not change the semantics of the module.
+        for (auto mask_bit :
+             {SpvLoopControlDependencyInfiniteMask,
+              SpvLoopControlDependencyLengthMask,
+              SpvLoopControlMinIterationsMask, SpvLoopControlMaxIterationsMask,
+              SpvLoopControlIterationMultipleMask}) {
+          if ((existing_mask & mask_bit) && GetFuzzerContext()->ChooseEven()) {
+            // The mask bits we are considering are not available in all SPIR-V
+            // versions.  However, we only include a mask bit if it was present
+            // in the original loop control mask, and we work under the
+            // assumption that we are transforming a valid module, thus we don't
+            // need to actually check whether the SPIR-V version being used
+            // supports these loop control mask bits.
+            new_mask |= mask_bit;
+          }
+        }
+
+        // We use 0 for peel count and partial count in the case that we choose
+        // not to set these controls.
+        uint32_t peel_count = 0;
+        uint32_t partial_count = 0;
+
+        // PeelCount and PartialCount are not compatible with DontUnroll, so
+        // we check whether DontUnroll is set.
+        if (!(new_mask & SpvLoopControlDontUnrollMask)) {
+          // If PeelCount is supported by this SPIR-V version, randomly choose
+          // whether to set it.  If it was set in the original mask and is not
+          // selected for setting here, that amounts to dropping it.
+          if (TransformationSetLoopControl::PeelCountIsSupported(
+                  GetIRContext()) &&
+              GetFuzzerContext()->ChooseEven()) {
+            new_mask |= SpvLoopControlPeelCountMask;
+            // The peel count is chosen randomly - if PeelCount was already set
+            // this will overwrite whatever peel count was previously used.
+            peel_count = GetFuzzerContext()->GetRandomLoopControlPeelCount();
+          }
+          // Similar, but for PartialCount.
+          if (TransformationSetLoopControl::PartialCountIsSupported(
+                  GetIRContext()) &&
+              GetFuzzerContext()->ChooseEven()) {
+            new_mask |= SpvLoopControlPartialCountMask;
+            partial_count =
+                GetFuzzerContext()->GetRandomLoopControlPartialCount();
+          }
+        }
+
+        // Apply the transformation and add it to the output transformation
+        // sequence.
+        TransformationSetLoopControl transformation(block.id(), new_mask,
+                                                    peel_count, partial_count);
+        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_loop_controls.h b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
new file mode 100644
index 0000000..e945606
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_adjust_loop_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_LOOP_CONTROLS_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that adjusts the loop controls on OpLoopMerge instructions.
+class FuzzerPassAdjustLoopControls : public FuzzerPass {
+ public:
+  FuzzerPassAdjustLoopControls(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAdjustLoopControls() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 0989502..80bb4df 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -23,8 +23,8 @@
 
 message IdUseDescriptor {
 
-  // Describes a use of an id as an input operand to an instruction in some block
-  // of a function.
+  // Describes a use of an id as an input operand to an instruction in some
+  // block of a function.
 
   // Example:
   //   - id_of_interest = 42
@@ -167,7 +167,8 @@
     TransformationAddTypeFloat add_type_float = 6;
     TransformationAddTypeInt add_type_int = 7;
     TransformationAddDeadBreak add_dead_break = 8;
-    TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9;
+    TransformationReplaceBooleanConstantWithConstantBinary
+      replace_boolean_constant_with_constant_binary = 9;
     TransformationAddTypePointer add_type_pointer = 10;
     TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
     TransformationAddDeadContinue add_dead_continue = 12;
@@ -175,6 +176,7 @@
     TransformationReplaceIdWithSynonym replace_id_with_synonym = 14;
     TransformationSetSelectionControl set_selection_control = 15;
     TransformationConstructComposite construct_composite = 16;
+    TransformationSetLoopControl set_loop_control = 17;
     // Add additional option using the next available number.
   }
 }
@@ -410,18 +412,42 @@
   uint32 fresh_id_for_temporary = 3;
 }
 
+message TransformationSetLoopControl {
+
+  // A transformation that sets the loop control operand of an OpLoopMerge
+  // instruction.
+
+  // The id of a basic block that should contain OpLoopMerge
+  uint32 block_id = 1;
+
+  // The value to which the 'loop control' operand should be set.
+  // This must be a legal loop control mask.
+  uint32 loop_control = 2;
+
+  // Provides a peel count value for the loop.  Used if and only if the
+  // PeelCount bit is set.  Must be zero if the PeelCount bit is not set (can
+  // still be zero if this bit is set).
+  uint32 peel_count = 3;
+
+  // Provides a partial count value for the loop.  Used if and only if the
+  // PartialCount bit is set.  Must be zero if the PartialCount bit is not set
+  // (can still be zero if this bit is set).
+  uint32 partial_count = 4;
+
+}
+
 message TransformationSetSelectionControl {
 
   // A transformation that sets the selection control operand of an
   // OpSelectionMerge instruction.
 
-  // The id of a basic block that should contain OpSelectionMerge.
+  // 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.
+  // 'Flatten | DontFlatten' does not make sense and is not allowed here.
   uint32 selection_control = 2;
 
 }
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 18c6abc..8b93b6a 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,6 +30,7 @@
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
+#include "source/fuzz/transformation_set_loop_control.h"
 #include "source/fuzz/transformation_set_selection_control.h"
 #include "source/fuzz/transformation_split_block.h"
 #include "source/util/make_unique.h"
@@ -81,6 +82,9 @@
     case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
       return MakeUnique<TransformationReplaceIdWithSynonym>(
           message.replace_id_with_synonym());
+    case protobufs::Transformation::TransformationCase::kSetLoopControl:
+      return MakeUnique<TransformationSetLoopControl>(
+          message.set_loop_control());
     case protobufs::Transformation::TransformationCase::kSetSelectionControl:
       return MakeUnique<TransformationSetSelectionControl>(
           message.set_selection_control());
diff --git a/source/fuzz/transformation_set_loop_control.cpp b/source/fuzz/transformation_set_loop_control.cpp
new file mode 100644
index 0000000..9062f17
--- /dev/null
+++ b/source/fuzz/transformation_set_loop_control.cpp
@@ -0,0 +1,216 @@
+// 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_loop_control.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSetLoopControl::TransformationSetLoopControl(
+    const spvtools::fuzz::protobufs::TransformationSetLoopControl& message)
+    : message_(message) {}
+
+TransformationSetLoopControl::TransformationSetLoopControl(
+    uint32_t block_id, uint32_t loop_control, uint32_t peel_count,
+    uint32_t partial_count) {
+  message_.set_block_id(block_id);
+  message_.set_loop_control(loop_control);
+  message_.set_peel_count(peel_count);
+  message_.set_partial_count(partial_count);
+}
+
+bool TransformationSetLoopControl::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  // |message_.block_id| must identify a block that ends with OpLoopMerge.
+  auto block = context->get_instr_block(message_.block_id());
+  if (!block) {
+    return false;
+  }
+  auto merge_inst = block->GetMergeInst();
+  if (!merge_inst || merge_inst->opcode() != SpvOpLoopMerge) {
+    return false;
+  }
+
+  // We sanity-check that the transformation does not try to set any meaningless
+  // bits of the loop control mask.
+  uint32_t all_loop_control_mask_bits_set =
+      SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask |
+      SpvLoopControlDependencyInfiniteMask |
+      SpvLoopControlDependencyLengthMask | SpvLoopControlMinIterationsMask |
+      SpvLoopControlMaxIterationsMask | SpvLoopControlIterationMultipleMask |
+      SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask;
+
+  // The variable is only used in an assertion; the following keeps release-mode
+  // compilers happy.
+  (void)(all_loop_control_mask_bits_set);
+
+  // No additional bits should be set.
+  assert(!(message_.loop_control() & ~all_loop_control_mask_bits_set));
+
+  // Grab the loop control mask currently associated with the OpLoopMerge
+  // instruction.
+  auto existing_loop_control_mask =
+      merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
+
+  // Check that there is no attempt to set one of the loop controls that
+  // requires guarantees to hold.
+  for (SpvLoopControlMask mask :
+       {SpvLoopControlDependencyInfiniteMask,
+        SpvLoopControlDependencyLengthMask, SpvLoopControlMinIterationsMask,
+        SpvLoopControlMaxIterationsMask, SpvLoopControlIterationMultipleMask}) {
+    // We have a problem if this loop control bit was not set in the original
+    // loop control mask but is set by the transformation.
+    if (LoopControlBitIsAddedByTransformation(mask,
+                                              existing_loop_control_mask)) {
+      return false;
+    }
+  }
+
+  if ((message_.loop_control() &
+       (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)) &&
+      !(PeelCountIsSupported(context) && PartialCountIsSupported(context))) {
+    // At least one of PeelCount or PartialCount is used, but the SPIR-V version
+    // in question does not support these loop controls.
+    return false;
+  }
+
+  if (message_.peel_count() > 0 &&
+      !(message_.loop_control() & SpvLoopControlPeelCountMask)) {
+    // Peel count provided, but peel count mask bit not set.
+    return false;
+  }
+
+  if (message_.partial_count() > 0 &&
+      !(message_.loop_control() & SpvLoopControlPartialCountMask)) {
+    // Partial count provided, but partial count mask bit not set.
+    return false;
+  }
+
+  // We must not set both 'don't unroll' and one of 'peel count' or 'partial
+  // count'.
+  return !((message_.loop_control() & SpvLoopControlDontUnrollMask) &&
+           (message_.loop_control() &
+            (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)));
+}
+
+void TransformationSetLoopControl::Apply(opt::IRContext* context,
+                                         FactManager* /*unused*/) const {
+  // Grab the loop merge instruction and its associated loop control mask.
+  auto merge_inst =
+      context->get_instr_block(message_.block_id())->GetMergeInst();
+  auto existing_loop_control_mask =
+      merge_inst->GetSingleWordInOperand(kLoopControlMaskInOperandIndex);
+
+  // We are going to replace the OpLoopMerge's operands with this list.
+  opt::Instruction::OperandList new_operands;
+  // We add the existing merge block and continue target ids.
+  new_operands.push_back(merge_inst->GetInOperand(0));
+  new_operands.push_back(merge_inst->GetInOperand(1));
+  // We use the loop control mask from the transformation.
+  new_operands.push_back(
+      {SPV_OPERAND_TYPE_LOOP_CONTROL, {message_.loop_control()}});
+
+  // It remains to determine what literals to provide, in association with
+  // the new loop control mask.
+  //
+  // For the loop controls that require guarantees to hold about the number
+  // of loop iterations, we need to keep, from the original OpLoopMerge, any
+  // literals associated with loop control bits that are still set.
+
+  uint32_t literal_index = 0;  // Indexes into the literals from the original
+  // instruction.
+  for (SpvLoopControlMask mask :
+       {SpvLoopControlDependencyLengthMask, SpvLoopControlMinIterationsMask,
+        SpvLoopControlMaxIterationsMask, SpvLoopControlIterationMultipleMask}) {
+    // Check whether the bit was set in the original loop control mask.
+    if (existing_loop_control_mask & mask) {
+      // Check whether the bit is set in the new loop control mask.
+      if (message_.loop_control() & mask) {
+        // Add the associated literal to our sequence of replacement operands.
+        new_operands.push_back(
+            {SPV_OPERAND_TYPE_LITERAL_INTEGER,
+             {merge_inst->GetSingleWordInOperand(
+                 kLoopControlFirstLiteralInOperandIndex + literal_index)}});
+      }
+      // Increment our index into the original loop control mask's literals,
+      // whether or not the bit was set in the new mask.
+      literal_index++;
+    }
+  }
+
+  // If PeelCount is set in the new mask, |message_.peel_count| provides the
+  // associated peel count.
+  if (message_.loop_control() & SpvLoopControlPeelCountMask) {
+    new_operands.push_back(
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.peel_count()}});
+  }
+
+  // Similar, but for PartialCount.
+  if (message_.loop_control() & SpvLoopControlPartialCountMask) {
+    new_operands.push_back(
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.partial_count()}});
+  }
+
+  // Replace the input operands of the OpLoopMerge with the new operands we have
+  // accumulated.
+  merge_inst->SetInOperands(std::move(new_operands));
+}
+
+protobufs::Transformation TransformationSetLoopControl::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_set_loop_control() = message_;
+  return result;
+}
+
+bool TransformationSetLoopControl::LoopControlBitIsAddedByTransformation(
+    SpvLoopControlMask loop_control_single_bit_mask,
+    uint32_t existing_loop_control_mask) const {
+  return !(loop_control_single_bit_mask & existing_loop_control_mask) &&
+         (loop_control_single_bit_mask & message_.loop_control());
+}
+
+bool TransformationSetLoopControl::PartialCountIsSupported(
+    opt::IRContext* context) {
+  // TODO(afd): We capture the universal environments for which this loop
+  //  control is definitely not supported.  The check should be refined on
+  //  demand for other target environments.
+  switch (context->grammar().target_env()) {
+    case SPV_ENV_UNIVERSAL_1_0:
+    case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_UNIVERSAL_1_2:
+    case SPV_ENV_UNIVERSAL_1_3:
+      return false;
+    default:
+      return true;
+  }
+}
+
+bool TransformationSetLoopControl::PeelCountIsSupported(
+    opt::IRContext* context) {
+  // TODO(afd): We capture the universal environments for which this loop
+  //  control is definitely not supported.  The check should be refined on
+  //  demand for other target environments.
+  switch (context->grammar().target_env()) {
+    case SPV_ENV_UNIVERSAL_1_0:
+    case SPV_ENV_UNIVERSAL_1_1:
+    case SPV_ENV_UNIVERSAL_1_2:
+    case SPV_ENV_UNIVERSAL_1_3:
+      return false;
+    default:
+      return true;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_set_loop_control.h b/source/fuzz/transformation_set_loop_control.h
new file mode 100644
index 0000000..28b148c
--- /dev/null
+++ b/source/fuzz/transformation_set_loop_control.h
@@ -0,0 +1,79 @@
+// 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_LOOP_CONTROL_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_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 TransformationSetLoopControl : public Transformation {
+ public:
+  const static uint32_t kLoopControlMaskInOperandIndex = 2;
+  const static uint32_t kLoopControlFirstLiteralInOperandIndex = 3;
+
+  explicit TransformationSetLoopControl(
+      const protobufs::TransformationSetLoopControl& message);
+
+  TransformationSetLoopControl(uint32_t block_id, uint32_t loop_control,
+                               uint32_t peel_count, uint32_t partial_count);
+
+  // - |message_.block_id| must be a block containing an OpLoopMerge
+  //   instruction.
+  // - |message_.loop_control| must be a legal loop control mask that
+  //   only uses controls available in the SPIR-V version associated with
+  //   |context|, and must not add loop controls that are only valid in the
+  //   presence of guarantees about what the loop does (e.g. MinIterations).
+  // - |message_.peel_count| (respectively |message_.partial_count|) must be
+  //   zero PeelCount (respectively PartialCount) is set in
+  //   |message_.loop_control|.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - The loop control operand of the OpLoopMergeInstruction in
+  //   |message_.block_id| is overwritten with |message_.loop_control|.
+  // - The literals associated with the loop control are updated to reflect any
+  //   controls with associated literals that have been removed (e.g.
+  //   MinIterations), and any that have been added (PeelCount and/or
+  //   PartialCount).
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Does the version of SPIR-V being used support the PartialCount loop
+  // control?
+  static bool PartialCountIsSupported(opt::IRContext* context);
+
+  // Does the version of SPIR-V being used support the PeelCount loop control?
+  static bool PeelCountIsSupported(opt::IRContext* context);
+
+ private:
+  // Returns true if and only if |loop_single_bit_mask| is *not* set in
+  // |existing_loop_control| but *is* set in |message_.loop_control|.
+  bool LoopControlBitIsAddedByTransformation(
+      SpvLoopControlMask loop_control_single_bit_mask,
+      uint32_t existing_loop_control_mask) const;
+
+  protobufs::TransformationSetLoopControl message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SET_LOOP_CONTROL_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index ca51b48..f4405e8 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -34,6 +34,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_loop_control_test.cpp
           transformation_set_selection_control_test.cpp
           transformation_split_block_test.cpp
           uniform_buffer_element_descriptor_test.cpp)
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index c2a1984..fed6cfe 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -31,7 +31,7 @@
 void RunFuzzerAndReplayer(const std::string& shader,
                           const protobufs::FactSequence& initial_facts,
                           uint32_t initial_seed, uint32_t num_runs) {
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -305,7 +305,7 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %16 %139
+               OpEntryPoint Fragment %4 "main" %16 %139 %25 %68
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpName %4 "main"
@@ -605,7 +605,7 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %68 %100
+               OpEntryPoint Fragment %4 "main" %68 %100 %24
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpName %4 "main"
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index 84608f3..6485070 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -154,7 +154,7 @@
 void RunFuzzerAndShrinker(const std::string& shader,
                           const protobufs::FactSequence& initial_facts,
                           uint32_t seed) {
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
@@ -434,7 +434,7 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %16 %139
+               OpEntryPoint Fragment %4 "main" %16 %139 %25 %68
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpName %4 "main"
@@ -732,7 +732,7 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %68 %100
+               OpEntryPoint Fragment %4 "main" %68 %100 %24
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpName %4 "main"
diff --git a/test/fuzz/transformation_set_loop_control_test.cpp b/test/fuzz/transformation_set_loop_control_test.cpp
new file mode 100644
index 0000000..83953ec
--- /dev/null
+++ b/test/fuzz/transformation_set_loop_control_test.cpp
@@ -0,0 +1,968 @@
+// 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_loop_control.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSetLoopControlTest, VariousScenarios) {
+  // This test features loops with various different controls, and goes through
+  // a number of acceptable and unacceptable transformations to those controls.
+
+  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
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %42 = OpVariable %7 Function
+         %52 = OpVariable %7 Function
+         %62 = OpVariable %7 Function
+         %72 = OpVariable %7 Function
+         %82 = OpVariable %7 Function
+         %92 = OpVariable %7 Function
+        %102 = OpVariable %7 Function
+        %112 = OpVariable %7 Function
+        %122 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+        %132 = OpPhi %6 %9 %5 %21 %13
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %18 = OpSLessThan %17 %132 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %21 = OpIAdd %6 %132 %20
+               OpStore %8 %21
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %22 %9
+               OpBranch %23
+         %23 = OpLabel
+        %133 = OpPhi %6 %9 %12 %31 %26
+               OpLoopMerge %25 %26 Unroll
+               OpBranch %27
+         %27 = OpLabel
+         %29 = OpSLessThan %17 %133 %16
+               OpBranchConditional %29 %24 %25
+         %24 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+         %31 = OpIAdd %6 %133 %20
+               OpStore %22 %31
+               OpBranch %23
+         %25 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+        %134 = OpPhi %6 %9 %25 %41 %36
+               OpLoopMerge %35 %36 DontUnroll
+               OpBranch %37
+         %37 = OpLabel
+         %39 = OpSLessThan %17 %134 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %41 = OpIAdd %6 %134 %20
+               OpStore %32 %41
+               OpBranch %33
+         %35 = OpLabel
+               OpStore %42 %9
+               OpBranch %43
+         %43 = OpLabel
+        %135 = OpPhi %6 %9 %35 %51 %46
+               OpLoopMerge %45 %46 DependencyInfinite
+               OpBranch %47
+         %47 = OpLabel
+         %49 = OpSLessThan %17 %135 %16
+               OpBranchConditional %49 %44 %45
+         %44 = OpLabel
+               OpBranch %46
+         %46 = OpLabel
+         %51 = OpIAdd %6 %135 %20
+               OpStore %42 %51
+               OpBranch %43
+         %45 = OpLabel
+               OpStore %52 %9
+               OpBranch %53
+         %53 = OpLabel
+        %136 = OpPhi %6 %9 %45 %61 %56
+               OpLoopMerge %55 %56 DependencyLength 3
+               OpBranch %57
+         %57 = OpLabel
+         %59 = OpSLessThan %17 %136 %16
+               OpBranchConditional %59 %54 %55
+         %54 = OpLabel
+               OpBranch %56
+         %56 = OpLabel
+         %61 = OpIAdd %6 %136 %20
+               OpStore %52 %61
+               OpBranch %53
+         %55 = OpLabel
+               OpStore %62 %9
+               OpBranch %63
+         %63 = OpLabel
+        %137 = OpPhi %6 %9 %55 %71 %66
+               OpLoopMerge %65 %66 MinIterations 10
+               OpBranch %67
+         %67 = OpLabel
+         %69 = OpSLessThan %17 %137 %16
+               OpBranchConditional %69 %64 %65
+         %64 = OpLabel
+               OpBranch %66
+         %66 = OpLabel
+         %71 = OpIAdd %6 %137 %20
+               OpStore %62 %71
+               OpBranch %63
+         %65 = OpLabel
+               OpStore %72 %9
+               OpBranch %73
+         %73 = OpLabel
+        %138 = OpPhi %6 %9 %65 %81 %76
+               OpLoopMerge %75 %76 MaxIterations 50
+               OpBranch %77
+         %77 = OpLabel
+         %79 = OpSLessThan %17 %138 %16
+               OpBranchConditional %79 %74 %75
+         %74 = OpLabel
+               OpBranch %76
+         %76 = OpLabel
+         %81 = OpIAdd %6 %138 %20
+               OpStore %72 %81
+               OpBranch %73
+         %75 = OpLabel
+               OpStore %82 %9
+               OpBranch %83
+         %83 = OpLabel
+        %139 = OpPhi %6 %9 %75 %91 %86
+               OpLoopMerge %85 %86 IterationMultiple 4
+               OpBranch %87
+         %87 = OpLabel
+         %89 = OpSLessThan %17 %139 %16
+               OpBranchConditional %89 %84 %85
+         %84 = OpLabel
+               OpBranch %86
+         %86 = OpLabel
+         %91 = OpIAdd %6 %139 %20
+               OpStore %82 %91
+               OpBranch %83
+         %85 = OpLabel
+               OpStore %92 %9
+               OpBranch %93
+         %93 = OpLabel
+        %140 = OpPhi %6 %9 %85 %101 %96
+               OpLoopMerge %95 %96 PeelCount 2
+               OpBranch %97
+         %97 = OpLabel
+         %99 = OpSLessThan %17 %140 %16
+               OpBranchConditional %99 %94 %95
+         %94 = OpLabel
+               OpBranch %96
+         %96 = OpLabel
+        %101 = OpIAdd %6 %140 %20
+               OpStore %92 %101
+               OpBranch %93
+         %95 = OpLabel
+               OpStore %102 %9
+               OpBranch %103
+        %103 = OpLabel
+        %141 = OpPhi %6 %9 %95 %111 %106
+               OpLoopMerge %105 %106 PartialCount 3
+               OpBranch %107
+        %107 = OpLabel
+        %109 = OpSLessThan %17 %141 %16
+               OpBranchConditional %109 %104 %105
+        %104 = OpLabel
+               OpBranch %106
+        %106 = OpLabel
+        %111 = OpIAdd %6 %141 %20
+               OpStore %102 %111
+               OpBranch %103
+        %105 = OpLabel
+               OpStore %112 %9
+               OpBranch %113
+        %113 = OpLabel
+        %142 = OpPhi %6 %9 %105 %121 %116
+               OpLoopMerge %115 %116 Unroll|PeelCount|PartialCount 3 4
+               OpBranch %117
+        %117 = OpLabel
+        %119 = OpSLessThan %17 %142 %16
+               OpBranchConditional %119 %114 %115
+        %114 = OpLabel
+               OpBranch %116
+        %116 = OpLabel
+        %121 = OpIAdd %6 %142 %20
+               OpStore %112 %121
+               OpBranch %113
+        %115 = OpLabel
+               OpStore %122 %9
+               OpBranch %123
+        %123 = OpLabel
+        %143 = OpPhi %6 %9 %115 %131 %126
+               OpLoopMerge %125 %126 DependencyLength|MinIterations|MaxIterations|IterationMultiple|PeelCount|PartialCount 2 5 90 4 7 14
+               OpBranch %127
+        %127 = OpLabel
+        %129 = OpSLessThan %17 %143 %16
+               OpBranchConditional %129 %124 %125
+        %124 = OpLabel
+               OpBranch %126
+        %126 = OpLabel
+        %131 = OpIAdd %6 %143 %20
+               OpStore %122 %131
+               OpBranch %123
+        %125 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // These are the loop headers together with the selection controls of their
+  // merge instructions:
+  //  %10 None
+  //  %23 Unroll
+  //  %33 DontUnroll
+  //  %43 DependencyInfinite
+  //  %53 DependencyLength 3
+  //  %63 MinIterations 10
+  //  %73 MaxIterations 50
+  //  %83 IterationMultiple 4
+  //  %93 PeelCount 2
+  // %103 PartialCount 3
+  // %113 Unroll|PeelCount|PartialCount 3 4
+  // %123
+  // DependencyLength|MinIterations|MaxIterations|IterationMultiple|PeelCount|PartialCount
+  // 2 5 90 4 7 14
+
+  ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(10, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(10, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   10, SpvLoopControlDependencyInfiniteMask, 0, 0)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(10, SpvLoopControlDependencyLengthMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(10, SpvLoopControlMinIterationsMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(10, SpvLoopControlMaxIterationsMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   10, SpvLoopControlIterationMultipleMask, 0, 0)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(10, SpvLoopControlPeelCountMask, 3, 3)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 0, 3)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(10, SpvLoopControlPartialCountMask, 3, 3)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(
+                  10,
+                  SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+                  3, 3)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(10,
+                                           SpvLoopControlUnrollMask |
+                                               SpvLoopControlPeelCountMask |
+                                               SpvLoopControlPartialCountMask,
+                                           3, 3)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(10,
+                                            SpvLoopControlDontUnrollMask |
+                                                SpvLoopControlPeelCountMask |
+                                                SpvLoopControlPartialCountMask,
+                                            3, 3)
+                   .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(23, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(
+                  23,
+                  SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+                  3, 3)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(23, SpvLoopControlMaxIterationsMask, 2, 3)
+          .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(33, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(33, SpvLoopControlMinIterationsMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          33, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(33,
+                                            SpvLoopControlDontUnrollMask |
+                                                SpvLoopControlPartialCountMask,
+                                            0, 10)
+                   .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(43, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(43, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(
+                  43,
+                  SpvLoopControlMaskNone | SpvLoopControlDependencyInfiniteMask,
+                  0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          43, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
+          0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          43,
+          SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
+          0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(43,
+                                   SpvLoopControlDependencyInfiniteMask |
+                                       SpvLoopControlDependencyLengthMask,
+                                   0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          43, SpvLoopControlUnrollMask | SpvLoopControlPeelCountMask, 5, 0)
+          .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(53, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(53, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(53, SpvLoopControlMaxIterationsMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          53, SpvLoopControlMaskNone | SpvLoopControlDependencyLengthMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(
+          53, SpvLoopControlUnrollMask | SpvLoopControlDependencyInfiniteMask,
+          0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          53, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyLengthMask,
+          0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(53,
+                                   SpvLoopControlDependencyInfiniteMask |
+                                       SpvLoopControlDependencyLengthMask,
+                                   0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          53,
+          SpvLoopControlUnrollMask | SpvLoopControlDependencyLengthMask |
+              SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+          5, 3)
+          .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(63, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(63, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(63,
+                                           SpvLoopControlUnrollMask |
+                                               SpvLoopControlMinIterationsMask |
+                                               SpvLoopControlPeelCountMask |
+                                               SpvLoopControlPartialCountMask,
+                                           5, 3)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(63,
+                                           SpvLoopControlUnrollMask |
+                                               SpvLoopControlMinIterationsMask |
+                                               SpvLoopControlPeelCountMask,
+                                           23, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   63,
+                   SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+                       SpvLoopControlPeelCountMask,
+                   2, 23)
+                   .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(73, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(73, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   73,
+                   SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+                       SpvLoopControlPeelCountMask |
+                       SpvLoopControlPartialCountMask,
+                   5, 3)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(73,
+                                           SpvLoopControlUnrollMask |
+                                               SpvLoopControlMaxIterationsMask |
+                                               SpvLoopControlPeelCountMask,
+                                           23, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   73,
+                   SpvLoopControlUnrollMask | SpvLoopControlMaxIterationsMask |
+                       SpvLoopControlPeelCountMask,
+                   2, 23)
+                   .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(83, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   83,
+                   SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+                       SpvLoopControlPeelCountMask |
+                       SpvLoopControlPartialCountMask,
+                   5, 3)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(83,
+                                   SpvLoopControlUnrollMask |
+                                       SpvLoopControlIterationMultipleMask |
+                                       SpvLoopControlPeelCountMask,
+                                   23, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(83,
+                                   SpvLoopControlUnrollMask |
+                                       SpvLoopControlIterationMultipleMask |
+                                       SpvLoopControlPeelCountMask,
+                                   2, 23)
+          .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(93, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(93, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(93, SpvLoopControlPeelCountMask, 8, 8)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(93, SpvLoopControlPartialCountMask, 0, 8)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(
+                  93,
+                  SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+                  16, 8)
+                  .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(103, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(103, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(103,
+                                            SpvLoopControlDontUnrollMask |
+                                                SpvLoopControlPartialCountMask,
+                                            0, 60)
+                   .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(113, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(113, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(
+          113,
+          SpvLoopControlIterationMultipleMask | SpvLoopControlPeelCountMask, 12,
+          0)
+          .IsApplicable(context.get(), fact_manager));
+
+  ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlMaskNone, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(123, SpvLoopControlUnrollMask, 0, 0)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(123, SpvLoopControlDontUnrollMask, 0, 0)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(
+      TransformationSetLoopControl(
+          123,
+          SpvLoopControlMinIterationsMask | SpvLoopControlMaxIterationsMask |
+              SpvLoopControlIterationMultipleMask |
+              SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask,
+          7, 8)
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_TRUE(TransformationSetLoopControl(123,
+                                           SpvLoopControlUnrollMask |
+                                               SpvLoopControlMinIterationsMask |
+                                               SpvLoopControlMaxIterationsMask |
+                                               SpvLoopControlPartialCountMask,
+                                           0, 9)
+                  .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationSetLoopControl(
+                   123,
+                   SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+                       SpvLoopControlMaxIterationsMask |
+                       SpvLoopControlPartialCountMask,
+                   7, 9)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationSetLoopControl(
+          123,
+          SpvLoopControlDontUnrollMask | SpvLoopControlMinIterationsMask |
+              SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
+          7, 9)
+          .IsApplicable(context.get(), fact_manager));
+
+  TransformationSetLoopControl(10,
+                               SpvLoopControlUnrollMask |
+                                   SpvLoopControlPeelCountMask |
+                                   SpvLoopControlPartialCountMask,
+                               3, 3)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(
+      43, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
+      0, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(63,
+                               SpvLoopControlUnrollMask |
+                                   SpvLoopControlMinIterationsMask |
+                                   SpvLoopControlPeelCountMask,
+                               23, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(73,
+                               SpvLoopControlUnrollMask |
+                                   SpvLoopControlMaxIterationsMask |
+                                   SpvLoopControlPeelCountMask,
+                               23, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(
+      93, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 16, 8)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
+      .Apply(context.get(), &fact_manager);
+  TransformationSetLoopControl(
+      123,
+      SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+          SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
+      0, 9)
+      .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"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %42 = OpVariable %7 Function
+         %52 = OpVariable %7 Function
+         %62 = OpVariable %7 Function
+         %72 = OpVariable %7 Function
+         %82 = OpVariable %7 Function
+         %92 = OpVariable %7 Function
+        %102 = OpVariable %7 Function
+        %112 = OpVariable %7 Function
+        %122 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+        %132 = OpPhi %6 %9 %5 %21 %13
+               OpLoopMerge %12 %13 Unroll|PeelCount|PartialCount 3 3
+               OpBranch %14
+         %14 = OpLabel
+         %18 = OpSLessThan %17 %132 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %21 = OpIAdd %6 %132 %20
+               OpStore %8 %21
+               OpBranch %10
+         %12 = OpLabel
+               OpStore %22 %9
+               OpBranch %23
+         %23 = OpLabel
+        %133 = OpPhi %6 %9 %12 %31 %26
+               OpLoopMerge %25 %26 DontUnroll
+               OpBranch %27
+         %27 = OpLabel
+         %29 = OpSLessThan %17 %133 %16
+               OpBranchConditional %29 %24 %25
+         %24 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+         %31 = OpIAdd %6 %133 %20
+               OpStore %22 %31
+               OpBranch %23
+         %25 = OpLabel
+               OpStore %32 %9
+               OpBranch %33
+         %33 = OpLabel
+        %134 = OpPhi %6 %9 %25 %41 %36
+               OpLoopMerge %35 %36 Unroll
+               OpBranch %37
+         %37 = OpLabel
+         %39 = OpSLessThan %17 %134 %16
+               OpBranchConditional %39 %34 %35
+         %34 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %41 = OpIAdd %6 %134 %20
+               OpStore %32 %41
+               OpBranch %33
+         %35 = OpLabel
+               OpStore %42 %9
+               OpBranch %43
+         %43 = OpLabel
+        %135 = OpPhi %6 %9 %35 %51 %46
+               OpLoopMerge %45 %46 DontUnroll|DependencyInfinite
+               OpBranch %47
+         %47 = OpLabel
+         %49 = OpSLessThan %17 %135 %16
+               OpBranchConditional %49 %44 %45
+         %44 = OpLabel
+               OpBranch %46
+         %46 = OpLabel
+         %51 = OpIAdd %6 %135 %20
+               OpStore %42 %51
+               OpBranch %43
+         %45 = OpLabel
+               OpStore %52 %9
+               OpBranch %53
+         %53 = OpLabel
+        %136 = OpPhi %6 %9 %45 %61 %56
+               OpLoopMerge %55 %56 None
+               OpBranch %57
+         %57 = OpLabel
+         %59 = OpSLessThan %17 %136 %16
+               OpBranchConditional %59 %54 %55
+         %54 = OpLabel
+               OpBranch %56
+         %56 = OpLabel
+         %61 = OpIAdd %6 %136 %20
+               OpStore %52 %61
+               OpBranch %53
+         %55 = OpLabel
+               OpStore %62 %9
+               OpBranch %63
+         %63 = OpLabel
+        %137 = OpPhi %6 %9 %55 %71 %66
+               OpLoopMerge %65 %66 Unroll|MinIterations|PeelCount 10 23
+               OpBranch %67
+         %67 = OpLabel
+         %69 = OpSLessThan %17 %137 %16
+               OpBranchConditional %69 %64 %65
+         %64 = OpLabel
+               OpBranch %66
+         %66 = OpLabel
+         %71 = OpIAdd %6 %137 %20
+               OpStore %62 %71
+               OpBranch %63
+         %65 = OpLabel
+               OpStore %72 %9
+               OpBranch %73
+         %73 = OpLabel
+        %138 = OpPhi %6 %9 %65 %81 %76
+               OpLoopMerge %75 %76 Unroll|MaxIterations|PeelCount 50 23
+               OpBranch %77
+         %77 = OpLabel
+         %79 = OpSLessThan %17 %138 %16
+               OpBranchConditional %79 %74 %75
+         %74 = OpLabel
+               OpBranch %76
+         %76 = OpLabel
+         %81 = OpIAdd %6 %138 %20
+               OpStore %72 %81
+               OpBranch %73
+         %75 = OpLabel
+               OpStore %82 %9
+               OpBranch %83
+         %83 = OpLabel
+        %139 = OpPhi %6 %9 %75 %91 %86
+               OpLoopMerge %85 %86 DontUnroll
+               OpBranch %87
+         %87 = OpLabel
+         %89 = OpSLessThan %17 %139 %16
+               OpBranchConditional %89 %84 %85
+         %84 = OpLabel
+               OpBranch %86
+         %86 = OpLabel
+         %91 = OpIAdd %6 %139 %20
+               OpStore %82 %91
+               OpBranch %83
+         %85 = OpLabel
+               OpStore %92 %9
+               OpBranch %93
+         %93 = OpLabel
+        %140 = OpPhi %6 %9 %85 %101 %96
+               OpLoopMerge %95 %96 PeelCount|PartialCount 16 8
+               OpBranch %97
+         %97 = OpLabel
+         %99 = OpSLessThan %17 %140 %16
+               OpBranchConditional %99 %94 %95
+         %94 = OpLabel
+               OpBranch %96
+         %96 = OpLabel
+        %101 = OpIAdd %6 %140 %20
+               OpStore %92 %101
+               OpBranch %93
+         %95 = OpLabel
+               OpStore %102 %9
+               OpBranch %103
+        %103 = OpLabel
+        %141 = OpPhi %6 %9 %95 %111 %106
+               OpLoopMerge %105 %106 PartialCount 60
+               OpBranch %107
+        %107 = OpLabel
+        %109 = OpSLessThan %17 %141 %16
+               OpBranchConditional %109 %104 %105
+        %104 = OpLabel
+               OpBranch %106
+        %106 = OpLabel
+        %111 = OpIAdd %6 %141 %20
+               OpStore %102 %111
+               OpBranch %103
+        %105 = OpLabel
+               OpStore %112 %9
+               OpBranch %113
+        %113 = OpLabel
+        %142 = OpPhi %6 %9 %105 %121 %116
+               OpLoopMerge %115 %116 PeelCount 12
+               OpBranch %117
+        %117 = OpLabel
+        %119 = OpSLessThan %17 %142 %16
+               OpBranchConditional %119 %114 %115
+        %114 = OpLabel
+               OpBranch %116
+        %116 = OpLabel
+        %121 = OpIAdd %6 %142 %20
+               OpStore %112 %121
+               OpBranch %113
+        %115 = OpLabel
+               OpStore %122 %9
+               OpBranch %123
+        %123 = OpLabel
+        %143 = OpPhi %6 %9 %115 %131 %126
+               OpLoopMerge %125 %126 Unroll|MinIterations|MaxIterations|PartialCount 5 90 9
+               OpBranch %127
+        %127 = OpLabel
+        %129 = OpSLessThan %17 %143 %16
+               OpBranchConditional %129 %124 %125
+        %124 = OpLabel
+               OpBranch %126
+        %126 = OpLabel
+        %131 = OpIAdd %6 %143 %20
+               OpStore %122 %131
+               OpBranch %123
+        %125 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationSetLoopControlTest, CheckSPIRVVersionsRespected) {
+  // This test checks that we do not allow introducing PeelCount and
+  // PartialCount loop controls if the SPIR-V version being used does not
+  // support them.
+
+  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 1
+          %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
+               OpBranch %13
+         %13 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+               OpStore %8 %21
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto consumer = nullptr;
+  const auto context_1_0 =
+      BuildModule(SPV_ENV_UNIVERSAL_1_0, consumer, shader, kFuzzAssembleOption);
+  const auto context_1_1 =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, shader, kFuzzAssembleOption);
+  const auto context_1_2 =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer, shader, kFuzzAssembleOption);
+  const auto context_1_3 =
+      BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer, shader, kFuzzAssembleOption);
+  const auto context_1_4 =
+      BuildModule(SPV_ENV_UNIVERSAL_1_4, consumer, shader, kFuzzAssembleOption);
+  const auto context_1_5 =
+      BuildModule(SPV_ENV_UNIVERSAL_1_5, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  TransformationSetLoopControl set_peel_and_partial(
+      10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4);
+
+  // PeelCount and PartialCount were introduced in SPIRV 1.4, so are not valid
+  // in the context of older versions.
+  ASSERT_FALSE(
+      set_peel_and_partial.IsApplicable(context_1_0.get(), fact_manager));
+  ASSERT_FALSE(
+      set_peel_and_partial.IsApplicable(context_1_1.get(), fact_manager));
+  ASSERT_FALSE(
+      set_peel_and_partial.IsApplicable(context_1_2.get(), fact_manager));
+  ASSERT_FALSE(
+      set_peel_and_partial.IsApplicable(context_1_3.get(), fact_manager));
+
+  ASSERT_TRUE(
+      set_peel_and_partial.IsApplicable(context_1_4.get(), fact_manager));
+  ASSERT_TRUE(
+      set_peel_and_partial.IsApplicable(context_1_5.get(), fact_manager));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools