spirv-fuzz: Create synonym of int constant using a loop (#3790)

This transformation, given a constant integer (scalar or vector) C,
constants I and S of compatible type and scalar 32-bit integer constant
N, such that C = I - S*N, adds a loop which subtracts S from I, N
times, creating a synonym for C.

The related fuzzer pass randomly chooses constants to which to add
synonyms using this transformation, and the location where they should
be added.

Fixes #3616.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 733bc56..cea05cf 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -63,15 +63,16 @@
         fuzzer_pass_add_function_calls.h
         fuzzer_pass_add_global_variables.h
         fuzzer_pass_add_image_sample_unused_components.h
-        fuzzer_pass_add_synonyms.h
         fuzzer_pass_add_loads.h
         fuzzer_pass_add_local_variables.h
         fuzzer_pass_add_loop_preheaders.h
+        fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
         fuzzer_pass_add_no_contraction_decorations.h
         fuzzer_pass_add_opphi_synonyms.h
         fuzzer_pass_add_parameters.h
         fuzzer_pass_add_relaxed_decorations.h
         fuzzer_pass_add_stores.h
+        fuzzer_pass_add_synonyms.h
         fuzzer_pass_add_vector_shuffle_instructions.h
         fuzzer_pass_adjust_branch_weights.h
         fuzzer_pass_adjust_function_controls.h
@@ -147,6 +148,7 @@
         transformation_add_image_sample_unused_components.h
         transformation_add_local_variable.h
         transformation_add_loop_preheader.h
+        transformation_add_loop_to_create_int_constant_synonym.h
         transformation_add_no_contraction_decoration.h
         transformation_add_opphi_synonym.h
         transformation_add_parameter.h
@@ -237,15 +239,16 @@
         fuzzer_pass_add_function_calls.cpp
         fuzzer_pass_add_global_variables.cpp
         fuzzer_pass_add_image_sample_unused_components.cpp
-        fuzzer_pass_add_synonyms.cpp
         fuzzer_pass_add_loads.cpp
         fuzzer_pass_add_local_variables.cpp
         fuzzer_pass_add_loop_preheaders.cpp
+        fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
         fuzzer_pass_add_opphi_synonyms.cpp
         fuzzer_pass_add_parameters.cpp
         fuzzer_pass_add_relaxed_decorations.cpp
         fuzzer_pass_add_stores.cpp
+        fuzzer_pass_add_synonyms.cpp
         fuzzer_pass_add_vector_shuffle_instructions.cpp
         fuzzer_pass_adjust_branch_weights.cpp
         fuzzer_pass_adjust_function_controls.cpp
@@ -319,6 +322,7 @@
         transformation_add_image_sample_unused_components.cpp
         transformation_add_local_variable.cpp
         transformation_add_loop_preheader.cpp
+        transformation_add_loop_to_create_int_constant_synonym.cpp
         transformation_add_no_contraction_decoration.cpp
         transformation_add_opphi_synonym.cpp
         transformation_add_parameter.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 845c8c0..557ee55 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -35,6 +35,7 @@
 #include "source/fuzz/fuzzer_pass_add_loads.h"
 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
+#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
 #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_parameters.h"
@@ -238,6 +239,8 @@
     MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances);
+    MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>(
+        &pass_instances);
     MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 416ac71..9e9e78d 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -75,6 +75,8 @@
     50, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfCreatingIntSynonymsUsingLoops = {
+    5, 10};
 const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
     20, 50};
@@ -84,6 +86,8 @@
     30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
     {50, 95};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym = {50, 80};
 const std::pair<uint32_t, uint32_t> kChanceOfInliningFunction = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
     10, 90};
@@ -244,6 +248,8 @@
   chance_of_constructing_composite_ =
       ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
   chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
+  chance_of_creating_int_synonyms_using_loops_ =
+      ChooseBetweenMinAndMax(kChanceOfCreatingIntSynonymsUsingLoops);
   chance_of_donating_additional_module_ =
       ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
   chance_of_duplicating_region_with_selection_ =
@@ -254,6 +260,8 @@
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
   chance_of_going_deeper_when_making_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+  chance_of_having_two_blocks_in_loop_to_create_int_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym);
   chance_of_inlining_function_ =
       ChooseBetweenMinAndMax(kChanceOfInliningFunction);
   chance_of_interchanging_signedness_of_integer_operands_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 79042e7..b6f466f 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -204,6 +204,9 @@
     return chance_of_constructing_composite_;
   }
   uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
+  uint32_t GetChanceOfCreatingIntSynonymsUsingLoops() {
+    return chance_of_creating_int_synonyms_using_loops_;
+  }
   uint32_t GetChanceOfDonatingAdditionalModule() {
     return chance_of_donating_additional_module_;
   }
@@ -219,6 +222,9 @@
   uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
     return chance_of_going_deeper_when_making_access_chain_;
   }
+  uint32_t GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym() {
+    return chance_of_having_two_blocks_in_loop_to_create_int_synonym_;
+  }
   uint32_t GetChanceOfInliningFunction() {
     return chance_of_inlining_function_;
   }
@@ -342,6 +348,9 @@
   uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) {
     return random_generator_->RandomUint32(number_of_components);
   }
+  int64_t GetRandomValueForStepConstantInLoop() {
+    return random_generator_->RandomUint64(UINT64_MAX);
+  }
   uint32_t GetRandomLoopControlPartialCount() {
     return random_generator_->RandomUint32(max_loop_control_partial_count_);
   }
@@ -351,6 +360,9 @@
   uint32_t GetRandomLoopLimit() {
     return random_generator_->RandomUint32(max_loop_limit_);
   }
+  uint32_t GetRandomNumberOfLoopIterations(uint32_t max_num_iterations) {
+    return ChooseBetweenMinAndMax({1, max_num_iterations});
+  }
   uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) {
     assert(num_of_params < GetMaximumNumberOfFunctionParameters());
     return ChooseBetweenMinAndMax(
@@ -423,11 +435,13 @@
   uint32_t chance_of_choosing_workgroup_storage_class_;
   uint32_t chance_of_constructing_composite_;
   uint32_t chance_of_copying_object_;
+  uint32_t chance_of_creating_int_synonyms_using_loops_;
   uint32_t chance_of_donating_additional_module_;
   uint32_t chance_of_duplicating_region_with_selection_;
   uint32_t chance_of_flattening_conditional_branch_;
   uint32_t chance_of_going_deeper_to_insert_in_composite_;
   uint32_t chance_of_going_deeper_when_making_access_chain_;
+  uint32_t chance_of_having_two_blocks_in_loop_to_create_int_synonym_;
   uint32_t chance_of_inlining_function_;
   uint32_t chance_of_interchanging_signedness_of_integer_operands_;
   uint32_t chance_of_interchanging_zero_like_constants_;
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 96feef9..b1f39b7 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -35,6 +35,7 @@
 #include "source/fuzz/transformation_add_type_pointer.h"
 #include "source/fuzz/transformation_add_type_struct.h"
 #include "source/fuzz/transformation_add_type_vector.h"
+#include "source/fuzz/transformation_split_block.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -608,6 +609,29 @@
   return &*function->FindBlock(preheader_id);
 }
 
+opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable(
+    uint32_t block_id) {
+  auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id);
+  assert(block && "|block_id| must be a block label");
+  assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header");
+
+  // Find the first non-OpPhi and non-OpVariable instruction.
+  auto non_phi_or_var_inst = &*block->begin();
+  while (non_phi_or_var_inst->opcode() == SpvOpPhi ||
+         non_phi_or_var_inst->opcode() == SpvOpVariable) {
+    non_phi_or_var_inst = non_phi_or_var_inst->NextNode();
+  }
+
+  // Split the block.
+  uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationSplitBlock(
+      MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst),
+      new_block_id));
+
+  // We need to return the newly-created block.
+  return &*block->GetParent()->FindBlock(new_block_id);
+}
+
 uint32_t FuzzerPass::FindOrCreateLocalVariable(
     uint32_t pointer_type_id, uint32_t function_id,
     bool pointee_value_is_irrelevant) {
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 40471eb..a9aae09 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -293,6 +293,11 @@
   // reachable in the CFG (and thus has at least 2 predecessors).
   opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id);
 
+  // Returns the second block in the pair obtained by splitting |block_id| just
+  // after the last OpPhi or OpVariable instruction in it. Assumes that the
+  // block is not a loop header.
+  opt::BasicBlock* SplitBlockAfterOpPhiOrOpVariable(uint32_t block_id);
+
   // Returns the id of an available local variable (storage class Function) with
   // the fact PointeeValueIsIrrelevant set according to
   // |pointee_value_is_irrelevant|. If there is no such variable, it creates one
diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
new file mode 100644
index 0000000..1b286dd
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
@@ -0,0 +1,247 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h"
+
+#include "source/fuzz/call_graph.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+uint32_t kMaxNestingDepth = 4;
+}  // namespace
+
+FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+    FuzzerPassAddLoopsToCreateIntConstantSynonyms(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+    ~FuzzerPassAddLoopsToCreateIntConstantSynonyms() = default;
+
+void FuzzerPassAddLoopsToCreateIntConstantSynonyms::Apply() {
+  std::vector<uint32_t> constants;
+
+  // Choose the constants for which to create synonyms.
+  for (auto constant_def : GetIRContext()->GetConstants()) {
+    // Randomly decide whether to consider this constant.
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfCreatingIntSynonymsUsingLoops())) {
+      continue;
+    }
+
+    auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+        constant_def->result_id());
+
+    // We only consider integer constants (scalar or vector).
+    if (!constant->AsIntConstant() &&
+        !(constant->AsVectorConstant() &&
+          constant->AsVectorConstant()->component_type()->AsInteger())) {
+      continue;
+    }
+
+    constants.push_back(constant_def->result_id());
+  }
+
+  std::vector<uint32_t> blocks;
+
+  // Get a list of all the blocks before which we can add a loop creating a new
+  // synonym. We cannot apply the transformation while iterating over the
+  // module, because we are going to add new blocks.
+  for (auto& function : *GetIRContext()->module()) {
+    // Consider all blocks reachable from the first block of the function.
+    GetIRContext()->cfg()->ForEachBlockInPostOrder(
+        &*function.begin(),
+        [&blocks](opt::BasicBlock* block) { blocks.push_back(block->id()); });
+  }
+
+  // Make sure that the module has an OpTypeBool instruction, and 32-bit signed
+  // integer constants 0 and 1, adding them if necessary.
+  FindOrCreateBoolType();
+  FindOrCreateIntegerConstant({0}, 32, true, false);
+  FindOrCreateIntegerConstant({1}, 32, true, false);
+
+  // Compute the call graph. We can use this for any further computation, since
+  // we are not adding or removing functions or function calls.
+  auto call_graph = CallGraph(GetIRContext());
+
+  // Consider each constant and each block.
+  for (uint32_t constant_id : constants) {
+    // Choose one of the blocks.
+    uint32_t block_id = blocks[GetFuzzerContext()->RandomIndex(blocks)];
+
+    // Adjust the block so that the transformation can be applied.
+    auto block = GetIRContext()->get_instr_block(block_id);
+
+    // If the block is a loop header, add a simple preheader. We can do this
+    // because we have excluded all the non-reachable headers.
+    if (block->IsLoopHeader()) {
+      block = GetOrCreateSimpleLoopPreheader(block->id());
+      block_id = block->id();
+    }
+
+    assert(!block->IsLoopHeader() &&
+           "The block cannot be a loop header at this point.");
+
+    // If the block is a merge block, a continue block or it does not have
+    // exactly 1 predecessor, split it after any OpPhi or OpVariable
+    // instructions.
+    if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id()) ||
+        GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
+            block->id()) ||
+        GetIRContext()->cfg()->preds(block->id()).size() != 1) {
+      block = SplitBlockAfterOpPhiOrOpVariable(block->id());
+      block_id = block->id();
+    }
+
+    // Randomly decide the values for the number of iterations and the step
+    // value, and compute the initial value accordingly.
+
+    // The maximum number of iterations depends on the maximum possible loop
+    // nesting depth of the block, computed interprocedurally, i.e. also
+    // considering the possibility that the enclosing function is called inside
+    // a loop. It is:
+    // - 1 if the nesting depth is >= kMaxNestingDepth
+    // - 2^(kMaxNestingDepth - nesting_depth) otherwise
+    uint32_t max_nesting_depth =
+        call_graph.GetMaxCallNestingDepth(block->GetParent()->result_id()) +
+        GetIRContext()->GetStructuredCFGAnalysis()->LoopNestingDepth(
+            block->id());
+    uint32_t num_iterations =
+        max_nesting_depth >= kMaxNestingDepth
+            ? 1
+            : GetFuzzerContext()->GetRandomNumberOfLoopIterations(
+                  1u << (kMaxNestingDepth - max_nesting_depth));
+
+    // Find or create the corresponding constant containing the number of
+    // iterations.
+    uint32_t num_iterations_id =
+        FindOrCreateIntegerConstant({num_iterations}, 32, true, false);
+
+    // Find the other constants.
+    // We use 64-bit values and then use the bits that we need. We find the
+    // step value (S) randomly and then compute the initial value (I) using
+    // the equation I = C + S*N.
+    uint32_t initial_value_id = 0;
+    uint32_t step_value_id = 0;
+
+    // Get the content of the existing constant.
+    const auto constant =
+        GetIRContext()->get_constant_mgr()->FindDeclaredConstant(constant_id);
+    const auto constant_type_id =
+        GetIRContext()->get_def_use_mgr()->GetDef(constant_id)->type_id();
+
+    if (constant->AsIntConstant()) {
+      // The constant is a scalar integer.
+
+      std::tie(initial_value_id, step_value_id) =
+          FindSuitableStepAndInitialValueConstants(
+              constant->GetZeroExtendedValue(),
+              constant->type()->AsInteger()->width(),
+              constant->type()->AsInteger()->IsSigned(), num_iterations);
+    } else {
+      // The constant is a vector of integers.
+      assert(constant->AsVectorConstant() &&
+             constant->AsVectorConstant()->component_type()->AsInteger() &&
+             "If the program got here, the constant should be a vector of "
+             "integers.");
+
+      // Find a constant for each component of the initial value and the step
+      // values.
+      std::vector<uint32_t> initial_value_component_ids;
+      std::vector<uint32_t> step_value_component_ids;
+
+      // Get the value, width and signedness of the components.
+      std::vector<uint64_t> component_values;
+      for (auto component : constant->AsVectorConstant()->GetComponents()) {
+        component_values.push_back(component->GetZeroExtendedValue());
+      }
+      uint32_t bit_width =
+          constant->AsVectorConstant()->component_type()->AsInteger()->width();
+      uint32_t is_signed = constant->AsVectorConstant()
+                               ->component_type()
+                               ->AsInteger()
+                               ->IsSigned();
+
+      for (uint64_t component_val : component_values) {
+        uint32_t initial_val_id;
+        uint32_t step_val_id;
+        std::tie(initial_val_id, step_val_id) =
+            FindSuitableStepAndInitialValueConstants(component_val, bit_width,
+                                                     is_signed, num_iterations);
+        initial_value_component_ids.push_back(initial_val_id);
+        step_value_component_ids.push_back(step_val_id);
+      }
+
+      // Find or create the vector constants.
+      initial_value_id = FindOrCreateCompositeConstant(
+          initial_value_component_ids, constant_type_id, false);
+      step_value_id = FindOrCreateCompositeConstant(step_value_component_ids,
+                                                    constant_type_id, false);
+    }
+
+    assert(initial_value_id && step_value_id &&
+           "|initial_value_id| and |step_value_id| should have been defined.");
+
+    // Randomly decide whether to have two blocks (or just one) in the new
+    // loop.
+    uint32_t additional_block_id =
+        GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()
+                ->GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym())
+            ? GetFuzzerContext()->GetFreshId()
+            : 0;
+
+    // Add the loop and create the synonym.
+    ApplyTransformation(TransformationAddLoopToCreateIntConstantSynonym(
+        constant_id, initial_value_id, step_value_id, num_iterations_id,
+        block_id, GetFuzzerContext()->GetFreshId(),
+        GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+        GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+        GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+        additional_block_id));
+  }
+}
+
+std::pair<uint32_t, uint32_t> FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+    FindSuitableStepAndInitialValueConstants(uint64_t constant_val,
+                                             uint32_t bit_width, bool is_signed,
+                                             uint32_t num_iterations) {
+  // Choose the step value randomly and compute the initial value accordingly.
+  // The result of |initial_value| could overflow, but this is OK, since
+  // the transformation takes overflows into consideration (the equation still
+  // holds as long as the last |bit_width| bits of C and of (I-S*N) match).
+  uint64_t step_value =
+      GetFuzzerContext()->GetRandomValueForStepConstantInLoop();
+  uint64_t initial_value = constant_val + step_value * num_iterations;
+
+  uint32_t initial_val_id = FindOrCreateIntegerConstant(
+      fuzzerutil::IntToWords(initial_value, bit_width, is_signed), bit_width,
+      is_signed, false);
+
+  uint32_t step_val_id = FindOrCreateIntegerConstant(
+      fuzzerutil::IntToWords(step_value, bit_width, is_signed), bit_width,
+      is_signed, false);
+
+  return {initial_val_id, step_val_id};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
\ No newline at end of file
diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
new file mode 100644
index 0000000..ee98c4e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that adds synonyms for integer, scalar or vector, constants, by
+// adding loops that compute the same value by subtracting a value S from an
+// initial value I, and for N times, so that C = I - S*N.
+class FuzzerPassAddLoopsToCreateIntConstantSynonyms : public FuzzerPass {
+ public:
+  FuzzerPassAddLoopsToCreateIntConstantSynonyms(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddLoopsToCreateIntConstantSynonyms();
+
+  void Apply() override;
+
+ private:
+  // Returns a pair (initial_val_id, step_val_id) such that both ids are
+  // integer scalar constants of the same type as the scalar integer constant
+  // identified by the given |constant_val|, |bit_width| and signedness, and
+  // such that, if I is the value of initial_val_id, S is the value of
+  // step_val_id and C is the value of the constant, the equation (C = I - S *
+  // num_iterations) holds, (only considering the last |bit_width| bits of each
+  // constant).
+  std::pair<uint32_t, uint32_t> FindSuitableStepAndInitialValueConstants(
+      uint64_t constant_val, uint32_t bit_width, bool is_signed,
+      uint32_t num_iterations);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index b550a74..aca9587 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1306,6 +1306,30 @@
   UpdateModuleIdBound(ir_context, result_id);
 }
 
+std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width,
+                                 bool is_signed) {
+  assert(width <= 64 && "The bit width should not be more than 64 bits");
+
+  // Sign-extend or zero-extend the last |width| bits of |value|, depending on
+  // |is_signed|.
+  if (is_signed) {
+    // Sign-extend by shifting left and then shifting right, interpreting the
+    // integer as signed.
+    value = static_cast<int64_t>(value << (64 - width)) >> (64 - width);
+  } else {
+    // Zero-extend by shifting left and then shifting right, interpreting the
+    // integer as unsigned.
+    value = (value << (64 - width)) >> (64 - width);
+  }
+
+  std::vector<uint32_t> result;
+  result.push_back(static_cast<uint32_t>(value));
+  if (width > 32) {
+    result.push_back(static_cast<uint32_t>(value >> 32));
+  }
+  return result;
+}
+
 bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id,
                            uint32_t type2_id) {
   if (type1_id == type2_id) {
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index b4b9057..981d229 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -474,6 +474,17 @@
 void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
                    const std::vector<uint32_t>& component_type_ids);
 
+// Returns a vector of words representing the integer |value|, only considering
+// the last |width| bits. The last |width| bits are sign-extended if the value
+// is signed, zero-extended if it is unsigned.
+// |width| must be <= 64.
+// If |width| <= 32, returns a vector containing one value. If |width| > 64,
+// returns a vector containing two values, with the first one representing the
+// lower-order word of the value and the second one representing the
+// higher-order word.
+std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width,
+                                 bool is_signed);
+
 // Returns a bit pattern that represents a floating-point |value|.
 inline uint32_t FloatToWord(float value) {
   uint32_t result;
diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h
index fdcb055..57820a2 100644
--- a/source/fuzz/pass_management/repeated_pass_instances.h
+++ b/source/fuzz/pass_management/repeated_pass_instances.h
@@ -30,6 +30,7 @@
 #include "source/fuzz/fuzzer_pass_add_loads.h"
 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
+#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_parameters.h"
 #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
@@ -118,6 +119,7 @@
   REPEATED_PASS_INSTANCE(AddLoads);
   REPEATED_PASS_INSTANCE(AddLocalVariables);
   REPEATED_PASS_INSTANCE(AddLoopPreheaders);
+  REPEATED_PASS_INSTANCE(AddLoopsToCreateIntConstantSynonyms);
   REPEATED_PASS_INSTANCE(AddOpPhiSynonyms);
   REPEATED_PASS_INSTANCE(AddParameters);
   REPEATED_PASS_INSTANCE(AddRelaxedDecorations);
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
index 253504b..8c03807 100644
--- a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
@@ -122,6 +122,10 @@
         {pass_instances_->GetDuplicateRegionsWithSelections(),
          pass_instances_->GetOutlineFunctions()});
   }
+  if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) {
+    // - New synonyms can be applied
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
   if (&pass == pass_instances_->GetAddOpPhiSynonyms()) {
     // - New synonyms can be applied
     // - If OpPhi synonyms are introduced for blocks with dead predecessors, the
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index c616943..d052ccc 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -497,6 +497,7 @@
     TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75;
     TransformationFlattenConditionalBranch flatten_conditional_branch = 76;
     TransformationAddBitInstructionSynonym add_bit_instruction_synonym = 77;
+    TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78;
     // Add additional option using the next available number.
   }
 }
@@ -847,6 +848,97 @@
 
 }
 
+message TransformationAddLoopToCreateIntConstantSynonym {
+  // A transformation that uses a loop to create a synonym for an integer
+  // constant C (scalar or vector) using an initial value I, a step value S and
+  // a number of iterations N such that C = I - N * S. For each iteration, S is
+  // added to the total.
+  // The loop can be made up of one or two blocks, and it is inserted before a
+  // block with a single predecessor. In the one-block case, it is of the form:
+  //
+  //            %loop_id = OpLabel
+  //             %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id
+  //            %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id
+  //    %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id
+  // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1
+  //            %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id
+  //                       OpLoopMerge %block_after_loop_id %loop_id %none
+  //                       OpBranchConditional %cond_id %loop_id %block_after_loop_id
+  //
+  // A new OpPhi instruction is then added to %block_after_loop_id, as follows:
+  //
+  //  %block_after_loop_id = OpLabel
+  //               %syn_id = OpPhi %type_of_I %eventual_syn_id %loop_id
+  //
+  // This can be translated, assuming that N > 0, to:
+  // int syn = I;
+  // for (int ctr = 0; ctr < N; ctr++) syn = syn - S;
+  //
+  // All existing OpPhi instructions in %block_after_loop_id are also updated
+  // to reflect the fact that its predecessor is now %loop_id.
+
+  // The following are existing ids.
+
+  // The id of the integer constant C that we want a synonym of.
+  uint32 constant_id = 1;
+
+  // The id of the initial value integer constant I.
+  uint32 initial_val_id = 2;
+
+  // The id of the step value integer constant S.
+  uint32 step_val_id = 3;
+
+  // The id of the integer scalar constant, its value being the number of
+  // iterations N.
+  uint32 num_iterations_id = 4;
+
+  // The label id of the block before which the loop must be inserted.
+  uint32 block_after_loop_id = 5;
+
+
+  // The following are fresh ids.
+
+  // A fresh id for the synonym.
+  uint32 syn_id = 6;
+
+  // A fresh id for the label of the loop,
+  uint32 loop_id = 7;
+
+  // A fresh id for the counter.
+  uint32 ctr_id = 8;
+
+  // A fresh id taking the value I - S * ctr at the ctr-th iteration.
+  uint32 temp_id = 9;
+
+  // A fresh id taking the value I - S * (ctr + 1) at the ctr-th iteration, and
+  // thus I - S * N at the last iteration.
+  uint32 eventual_syn_id = 10;
+
+  // A fresh id for the incremented counter.
+  uint32 incremented_ctr_id = 11;
+
+  // A fresh id for the loop condition.
+  uint32 cond_id = 12;
+
+  // The instructions in the loop can also be laid out in two basic blocks, as follows:
+  //
+  //  %loop_id = OpLabel
+  //   %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id
+  //  %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id
+  //             OpLoopMerge %block_after_loop_id %additional_block_id None
+  //             OpBranch %additional_block_id
+  //
+  //  %additional_block_id = OpLabel
+  //      %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id
+  //   %incremented_ctr_id = OpIAdd %int %ctr_id %int_1
+  //              %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id
+  //                         OpBranchConditional %cond_id %loop_id %block_after_loop_id
+
+  // A fresh id for the additional block. If this is 0, it means that only one
+  // block is to be created.
+  uint32 additional_block_id = 13;
+}
+
 message TransformationAddNoContractionDecoration {
 
   // Applies OpDecorate NoContraction to the given result id
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 0b253ff..7301a89 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -33,6 +33,7 @@
 #include "source/fuzz/transformation_add_image_sample_unused_components.h"
 #include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_loop_preheader.h"
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
 #include "source/fuzz/transformation_add_opphi_synonym.h"
 #include "source/fuzz/transformation_add_parameter.h"
@@ -150,6 +151,10 @@
       return MakeUnique<TransformationAddLoopPreheader>(
           message.add_loop_preheader());
     case protobufs::Transformation::TransformationCase::
+        kAddLoopToCreateIntConstantSynonym:
+      return MakeUnique<TransformationAddLoopToCreateIntConstantSynonym>(
+          message.add_loop_to_create_int_constant_synonym());
+    case protobufs::Transformation::TransformationCase::
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
           message.add_no_contraction_decoration());
diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
new file mode 100644
index 0000000..fccfc34
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
@@ -0,0 +1,427 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+uint32_t kMaxNumOfIterations = 32;
+}
+
+TransformationAddLoopToCreateIntConstantSynonym::
+    TransformationAddLoopToCreateIntConstantSynonym(
+        const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
+            message)
+    : message_(message) {}
+
+TransformationAddLoopToCreateIntConstantSynonym::
+    TransformationAddLoopToCreateIntConstantSynonym(
+        uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
+        uint32_t num_iterations_id, uint32_t block_after_loop_id,
+        uint32_t syn_id, uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id,
+        uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id,
+        uint32_t additional_block_id) {
+  message_.set_constant_id(constant_id);
+  message_.set_initial_val_id(initial_val_id);
+  message_.set_step_val_id(step_val_id);
+  message_.set_num_iterations_id(num_iterations_id);
+  message_.set_block_after_loop_id(block_after_loop_id);
+  message_.set_syn_id(syn_id);
+  message_.set_loop_id(loop_id);
+  message_.set_ctr_id(ctr_id);
+  message_.set_temp_id(temp_id);
+  message_.set_eventual_syn_id(eventual_syn_id);
+  message_.set_incremented_ctr_id(incremented_ctr_id);
+  message_.set_cond_id(cond_id);
+  message_.set_additional_block_id(additional_block_id);
+}
+
+bool TransformationAddLoopToCreateIntConstantSynonym::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that |message_.constant_id|, |message_.initial_val_id| and
+  // |message_.step_val_id| are existing constants.
+
+  auto constant = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.constant_id());
+  auto initial_val = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.initial_val_id());
+  auto step_val = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.step_val_id());
+
+  if (!constant || !initial_val || !step_val) {
+    return false;
+  }
+
+  // Check that the type of |constant| is integer scalar or vector with integer
+  // components.
+  if (!constant->AsIntConstant() &&
+      (!constant->AsVectorConstant() ||
+       !constant->type()->AsVector()->element_type()->AsInteger())) {
+    return false;
+  }
+
+  // Check that the component bit width of |constant| is <= 64.
+  // Consider the width of the constant if it is an integer, of a single
+  // component if it is a vector.
+  uint32_t bit_width =
+      constant->AsIntConstant()
+          ? constant->type()->AsInteger()->width()
+          : constant->type()->AsVector()->element_type()->AsInteger()->width();
+  if (bit_width > 64) {
+    return false;
+  }
+
+  auto constant_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.constant_id());
+  auto initial_val_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id());
+  auto step_val_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.step_val_id());
+
+  // Check that |constant|, |initial_val| and |step_val| have the same type,
+  // with possibly different signedness.
+  if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(),
+                                         initial_val_def->type_id()) ||
+      !fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(),
+                                         step_val_def->type_id())) {
+    return false;
+  }
+
+  // |message_.num_iterations_id| is an integer constant with bit width 32.
+  auto num_iterations = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.num_iterations_id());
+
+  if (!num_iterations || !num_iterations->AsIntConstant() ||
+      num_iterations->type()->AsInteger()->width() != 32) {
+    return false;
+  }
+
+  // Check that the number of iterations is > 0 and <= 32.
+  uint32_t num_iterations_value =
+      num_iterations->AsIntConstant()->GetU32BitValue();
+
+  if (num_iterations_value == 0 || num_iterations_value > kMaxNumOfIterations) {
+    return false;
+  }
+
+  // Check that the module contains 32-bit signed integer scalar constants of
+  // value 0 and 1.
+  if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+                                           {0}, 32, true, false)) {
+    return false;
+  }
+
+  if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+                                           {1}, 32, true, false)) {
+    return false;
+  }
+
+  // Check that the module contains the Bool type.
+  if (!fuzzerutil::MaybeGetBoolType(ir_context)) {
+    return false;
+  }
+
+  // Check that the equation C = I - S * N is satisfied.
+
+  // Collect the components in vectors (if the constants are scalars, these
+  // vectors will contain the constants themselves).
+  std::vector<const opt::analysis::Constant*> c_components;
+  std::vector<const opt::analysis::Constant*> i_components;
+  std::vector<const opt::analysis::Constant*> s_components;
+  if (constant->AsIntConstant()) {
+    c_components.emplace_back(constant);
+    i_components.emplace_back(initial_val);
+    s_components.emplace_back(step_val);
+  } else {
+    // It is a vector: get all the components.
+    c_components = constant->AsVectorConstant()->GetComponents();
+    i_components = initial_val->AsVectorConstant()->GetComponents();
+    s_components = step_val->AsVectorConstant()->GetComponents();
+  }
+
+  // Check the value of the components satisfy the equation.
+  for (uint32_t i = 0; i < c_components.size(); i++) {
+    // Use 64-bits integers to be able to handle constants of any width <= 64.
+    uint64_t c_value = c_components[i]->AsIntConstant()->GetZeroExtendedValue();
+    uint64_t i_value = i_components[i]->AsIntConstant()->GetZeroExtendedValue();
+    uint64_t s_value = s_components[i]->AsIntConstant()->GetZeroExtendedValue();
+
+    uint64_t result = i_value - s_value * num_iterations_value;
+
+    // Use bit shifts to ignore the first bits in excess (if there are any). By
+    // shifting left, we discard the first |64 - bit_width| bits. By shifting
+    // right, we move the bits back to their correct position.
+    result = (result << (64 - bit_width)) >> (64 - bit_width);
+
+    if (c_value != result) {
+      return false;
+    }
+  }
+
+  // Check that |message_.block_after_loop_id| is the label of a block.
+  auto block =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.block_after_loop_id());
+
+  // Check that the block exists and has a single predecessor.
+  if (!block || ir_context->cfg()->preds(block->id()).size() != 1) {
+    return false;
+  }
+
+  // Check that the block is not a merge block.
+  if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+    return false;
+  }
+
+  // Check that the block is not a continue block.
+  if (ir_context->GetStructuredCFGAnalysis()->IsContinueBlock(block->id())) {
+    return false;
+  }
+
+  // Check that the block is not a loop header.
+  if (block->IsLoopHeader()) {
+    return false;
+  }
+
+  // Check all the fresh ids.
+  std::set<uint32_t> fresh_ids_used;
+  for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(),
+                      message_.temp_id(), message_.eventual_syn_id(),
+                      message_.incremented_ctr_id(), message_.cond_id()}) {
+    if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+                                                             &fresh_ids_used)) {
+      return false;
+    }
+  }
+
+  // Check the additional block id if it is non-zero.
+  return !message_.additional_block_id() ||
+         CheckIdIsFreshAndNotUsedByThisTransformation(
+             message_.additional_block_id(), ir_context, &fresh_ids_used);
+}
+
+void TransformationAddLoopToCreateIntConstantSynonym::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Find 32-bit signed integer constants 0 and 1.
+  uint32_t const_0_id = fuzzerutil::MaybeGetIntegerConstant(
+      ir_context, *transformation_context, {0}, 32, true, false);
+  auto const_0_def = ir_context->get_def_use_mgr()->GetDef(const_0_id);
+  uint32_t const_1_id = fuzzerutil::MaybeGetIntegerConstant(
+      ir_context, *transformation_context, {1}, 32, true, false);
+
+  // Retrieve the instruction defining the initial value constant.
+  auto initial_val_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id());
+
+  // Retrieve the block before which we want to insert the loop.
+  auto block_after_loop =
+      ir_context->get_instr_block(message_.block_after_loop_id());
+
+  // Find the predecessor of the block.
+  uint32_t pred_id =
+      ir_context->cfg()->preds(message_.block_after_loop_id())[0];
+
+  // Get the id for the last block in the new loop. It will be
+  // |message_.additional_block_id| if this is non_zero, |message_.loop_id|
+  // otherwise.
+  uint32_t last_loop_block_id = message_.additional_block_id()
+                                    ? message_.additional_block_id()
+                                    : message_.loop_id();
+
+  // Create the loop header block.
+  std::unique_ptr<opt::BasicBlock> loop_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.loop_id(),
+          opt::Instruction::OperandList{}));
+
+  // Add OpPhi instructions to retrieve the current value of the counter and of
+  // the temporary variable that will be decreased at each operation.
+  loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpPhi, const_0_def->type_id(), message_.ctr_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {const_0_id}},
+          {SPV_OPERAND_TYPE_ID, {pred_id}},
+          {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+  loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpPhi, initial_val_def->type_id(), message_.temp_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.initial_val_id()}},
+          {SPV_OPERAND_TYPE_ID, {pred_id}},
+          {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+  // Collect the other instructions in a list. These will be added to an
+  // additional block if |message_.additional_block_id| is defined, to the loop
+  // header otherwise.
+  std::vector<std::unique_ptr<opt::Instruction>> other_instructions;
+
+  // Add an instruction to subtract the step value from the temporary value.
+  // The value of this id will converge to the constant in the last iteration.
+  other_instructions.push_back(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpISub, initial_val_def->type_id(),
+      message_.eventual_syn_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.temp_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.step_val_id()}}}));
+
+  // Add an instruction to increment the counter.
+  other_instructions.push_back(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpIAdd, const_0_def->type_id(),
+      message_.incremented_ctr_id(),
+      opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {message_.ctr_id()}},
+                                    {SPV_OPERAND_TYPE_ID, {const_1_id}}}));
+
+  // Add an instruction to decide whether the condition holds.
+  other_instructions.push_back(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSLessThan, fuzzerutil::MaybeGetBoolType(ir_context),
+      message_.cond_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.num_iterations_id()}}}));
+
+  // Define the OpLoopMerge instruction for the loop header. The merge block is
+  // the existing block, the continue block is the last block in the loop
+  // (either the loop itself or the additional block).
+  std::unique_ptr<opt::Instruction> merge_inst = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpLoopMerge, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}},
+          {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}});
+
+  // Define a conditional branch instruction, branching to the loop header if
+  // the condition holds, and to the existing block otherwise. This instruction
+  // will be added to the last block in the loop.
+  std::unique_ptr<opt::Instruction> conditional_branch =
+      MakeUnique<opt::Instruction>(
+          ir_context, SpvOpBranchConditional, 0, 0,
+          opt::Instruction::OperandList{
+              {SPV_OPERAND_TYPE_ID, {message_.cond_id()}},
+              {SPV_OPERAND_TYPE_ID, {message_.loop_id()}},
+              {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}});
+
+  if (message_.additional_block_id()) {
+    // If an id for the additional block is specified, create an additional
+    // block, containing the instructions in the list and a branching
+    // instruction.
+
+    std::unique_ptr<opt::BasicBlock> additional_block =
+        MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpLabel, 0, message_.additional_block_id(),
+            opt::Instruction::OperandList{}));
+
+    for (auto& instruction : other_instructions) {
+      additional_block->AddInstruction(std::move(instruction));
+    }
+
+    additional_block->AddInstruction(std::move(conditional_branch));
+
+    // Add the merge instruction to the header.
+    loop_block->AddInstruction(std::move(merge_inst));
+
+    // Add an unconditional branch from the header to the additional block.
+    loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpBranch, 0, 0,
+        opt::Instruction::OperandList{
+            {SPV_OPERAND_TYPE_ID, {message_.additional_block_id()}}}));
+
+    // Insert the two loop blocks before the existing block.
+    block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block),
+                                                          block_after_loop);
+    block_after_loop->GetParent()->InsertBasicBlockBefore(
+        std::move(additional_block), block_after_loop);
+  } else {
+    // If no id for an additional block is specified, the loop will only be made
+    // up of one block, so we need to add all the instructions to it.
+
+    for (auto& instruction : other_instructions) {
+      loop_block->AddInstruction(std::move(instruction));
+    }
+
+    // Add the merge and conditional branch instructions.
+    loop_block->AddInstruction(std::move(merge_inst));
+    loop_block->AddInstruction(std::move(conditional_branch));
+
+    // Insert the header before the existing block.
+    block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block),
+                                                          block_after_loop);
+  }
+
+  // Update the branching instructions leading to this block.
+  ir_context->get_def_use_mgr()->ForEachUse(
+      message_.block_after_loop_id(),
+      [this](opt::Instruction* instruction, uint32_t operand_index) {
+        assert(instruction->opcode() != SpvOpLoopMerge &&
+               instruction->opcode() != SpvOpSelectionMerge &&
+               instruction->opcode() != SpvOpSwitch &&
+               "The block should not be referenced by OpLoopMerge, "
+               "OpSelectionMerge or OpSwitch instructions, by construction.");
+        // Replace all uses of the label inside branch instructions.
+        if (instruction->opcode() == SpvOpBranch ||
+            instruction->opcode() == SpvOpBranchConditional) {
+          instruction->SetOperand(operand_index, {message_.loop_id()});
+        }
+      });
+
+  // Update all the OpPhi instructions in the block after the loop: its
+  // predecessor is now the last block in the loop.
+  block_after_loop->ForEachPhiInst(
+      [last_loop_block_id](opt::Instruction* phi_inst) {
+        // Since the block only had one predecessor, the id of the predecessor
+        // is input operand 1.
+        phi_inst->SetInOperand(1, {last_loop_block_id});
+      });
+
+  // Add a new OpPhi instruction at the beginning of the block after the loop,
+  // defining the synonym of the constant. The type id will be the same as
+  // |message_.initial_value_id|, since this is the value that is decremented in
+  // the loop.
+  block_after_loop->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpPhi, initial_val_def->type_id(), message_.syn_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+  // Update the module id bound with all the fresh ids used.
+  for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(),
+                      message_.temp_id(), message_.eventual_syn_id(),
+                      message_.incremented_ctr_id(), message_.cond_id(),
+                      message_.cond_id(), message_.additional_block_id()}) {
+    fuzzerutil::UpdateModuleIdBound(ir_context, id);
+  }
+
+  // Since we changed the structure of the module, we need to invalidate all the
+  // analyses.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Record that |message_.syn_id| is synonymous with |message_.constant_id|.
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.syn_id(), {}),
+      MakeDataDescriptor(message_.constant_id(), {}), ir_context);
+}
+
+protobufs::Transformation
+TransformationAddLoopToCreateIntConstantSynonym::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_loop_to_create_int_constant_synonym() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
new file mode 100644
index 0000000..2914029
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationAddLoopToCreateIntConstantSynonym : public Transformation {
+ public:
+  explicit TransformationAddLoopToCreateIntConstantSynonym(
+      const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
+          message);
+
+  TransformationAddLoopToCreateIntConstantSynonym(
+      uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
+      uint32_t num_iterations_id, uint32_t block_after_loop_id, uint32_t syn_id,
+      uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id,
+      uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id,
+      uint32_t additional_block_id);
+
+  // - |message_.constant_id|, |message_.initial_value_id|,
+  //   |message_.step_val_id| are integer constants (scalar or vectors) with the
+  //   same type (with possibly different signedness, but same bit width, which
+  //   must be <= 64). Let their value be C, I, S respectively.
+  // - |message_.num_iterations_id| is a 32-bit integer scalar constant, with
+  //   value N > 0 and N <= 32.
+  // - The module contains 32-bit signed integer scalar constants of values 0
+  //   and 1.
+  // - The module contains the boolean type.
+  // - C = I - S * N
+  // - |message_.block_after_loop_id| is the label of a block which has a single
+  //   predecessor and which is not a merge block, a continue block or a loop
+  //   header.
+  // - |message_.additional_block_id| is either 0 or a valid fresh id, distinct
+  //   from the other fresh ids.
+  // - All of the other parameters are valid fresh ids.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds a loop to the module, defining a synonym of an integer (scalar or
+  // vector) constant. This id is marked as synonym with the original constant.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddLoopToCreateIntConstantSynonym message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 66299f7..a918b24 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -46,6 +46,7 @@
           transformation_add_image_sample_unused_components_test.cpp
           transformation_add_local_variable_test.cpp
           transformation_add_loop_preheader_test.cpp
+          transformation_add_loop_to_create_int_constant_synonym_test.cpp
           transformation_add_no_contraction_decoration_test.cpp
           transformation_add_opphi_synonym_test.cpp
           transformation_add_parameter_test.cpp
diff --git a/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp
new file mode 100644
index 0000000..d88ffae
--- /dev/null
+++ b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp
@@ -0,0 +1,957 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     ConstantsNotSuitable) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+         %36 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 -1
+          %7 = OpConstant %5 0
+          %8 = OpConstant %5 1
+          %9 = OpConstant %5 2
+         %10 = OpConstant %5 5
+         %11 = OpConstant %5 10
+         %12 = OpConstant %5 20
+         %13 = OpConstant %5 33
+         %14 = OpTypeVector %5 2
+         %15 = OpConstantComposite %14 %10 %11
+         %16 = OpConstantComposite %14 %12 %12
+         %17 = OpTypeVector %5 3
+         %18 = OpConstantComposite %17 %11 %7 %11
+         %19 = OpTypeInt 64 1
+         %20 = OpConstant %19 0
+         %21 = OpConstant %19 10
+         %22 = OpTypeVector %19 2
+         %23 = OpConstantComposite %22 %21 %20
+         %24 = OpTypeFloat 32
+         %25 = OpConstant %24 0
+         %26 = OpConstant %24 5
+         %27 = OpConstant %24 10
+         %28 = OpConstant %24 20
+         %29 = OpTypeVector %24 3
+         %30 = OpConstantComposite %29 %26 %27 %26
+         %31 = OpConstantComposite %29 %28 %28 %28
+         %32 = OpConstantComposite %29 %27 %25 %27
+          %2 = OpFunction %3 None %4
+         %33 = OpLabel
+         %34 = OpCopyObject %5 %11
+               OpBranch %35
+         %35 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Reminder: the first four parameters of the constructor are the constants
+  // with values for C, I, S, N respectively.
+
+  // %70 does not correspond to an id in the module.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   70, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %35 is not a constant.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   35, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %27, %28 and %26 are not integer constants, but scalar floats.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   27, 28, 26, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %32, %31 and %30 are not integer constants, but vector floats.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   32, 31, 30, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %18=(10, 0, 10) has 3 components, while %16=(20, 20) and %15=(5, 10)
+  // have 2.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   18, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %21 has bit width 64, while the width of %12 and %10 is 32.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   21, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %13 has component width 64, while the component width of %16 and %15 is 32.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   13, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %21 (N) is a 64-bit integer, not 32-bit.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 21, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %7 (N) has value 0, so N <= 0.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 7, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %6 (N) has value -1, so N <= 1.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 6, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %13 (N) has value 33, so N > 32.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 6, 13, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // C(%11)=10, I(%12)=20, S(%10)=5, N(%8)=1, so C=I-S*N does not hold, as
+  // 20-5*1=15.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   11, 12, 10, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // C(%15)=(5, 10), I(%16)=(20, 20), S(%15)=(5, 10), N(%8)=1, so C=I-S*N does
+  // not hold, as (20, 20)-1*(5, 10) = (15, 10).
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   15, 16, 15, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     MissingConstantsOrBoolType) {
+  {
+    // The shader is missing the boolean type.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+         %20 = OpConstant %5 0
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpConstant %5 5
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+    const auto env = SPV_ENV_UNIVERSAL_1_5;
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    FactManager fact_manager;
+    spvtools::ValidatorOptions validator_options;
+    TransformationContext transformation_context(&fact_manager,
+                                                 validator_options);
+
+    ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                     9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+  {
+    // The shader is missing a 32-bit integer 0 constant.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+         %20 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpConstant %5 5
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+    const auto env = SPV_ENV_UNIVERSAL_1_5;
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    FactManager fact_manager;
+    spvtools::ValidatorOptions validator_options;
+    TransformationContext transformation_context(&fact_manager,
+                                                 validator_options);
+
+    ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                     9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+  {
+    // The shader is missing a 32-bit integer 1 constant.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+         %20 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 0
+          %7 = OpConstant %5 2
+          %8 = OpConstant %5 5
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+    const auto env = SPV_ENV_UNIVERSAL_1_5;
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    FactManager fact_manager;
+    spvtools::ValidatorOptions validator_options;
+    TransformationContext transformation_context(&fact_manager,
+                                                 validator_options);
+
+    ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                     9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Simple) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+         %22 = OpPhi %7 %12 %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %17 %18
+         %17 = OpLabel
+         %23 = OpPhi %7 %13 %15
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %19 None
+               OpBranchConditional %6 %20 %19
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpLoopMerge %27 %25 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpBranchConditional %6 %24 %27
+         %27 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Block %14 has no predecessors.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 14, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %18 has more than one predecessor.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 18, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %16 is a merge block.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 16, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %25 is a continue block.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 25, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %19 has more than one predecessor.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 19, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %20 is a merge block.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 20, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %20 is supposed to be fresh, but it is not.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 20, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %100 is used twice.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 100, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %100 is used twice.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Only the last id (for the additional block) is optional, so the other ones
+  // cannot be 0.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 0, 101, 102, 103, 104, 105, 106, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // This transformation will create a synonym of constant %12 from a 1-block
+  // loop.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // This transformation will create a synonym of constant %12 from a 2-block
+  // loop.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 13, 11, 10, 17, 107, 108, 109, 110, 111, 112, 113, 114);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(107, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // This transformation will create a synonym of constant %12 from a 2-block
+  // loop.
+  auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 13, 11, 10, 26, 115, 116, 117, 118, 119, 120, 121, 0);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(115, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %14 %105 %101
+        %103 = OpPhi %7 %13 %14 %104 %101
+        %104 = OpISub %7 %103 %11
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %10
+               OpLoopMerge %15 %101 None
+               OpBranchConditional %106 %101 %15
+         %15 = OpLabel
+        %100 = OpPhi %7 %104 %101
+         %22 = OpPhi %7 %12 %101
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %108 %18
+        %108 = OpLabel
+        %109 = OpPhi %7 %8 %15 %112 %114
+        %110 = OpPhi %7 %13 %15 %111 %114
+               OpLoopMerge %17 %114 None
+               OpBranch %114
+        %114 = OpLabel
+        %111 = OpISub %7 %110 %11
+        %112 = OpIAdd %7 %109 %9
+        %113 = OpSLessThan %5 %112 %10
+               OpBranchConditional %113 %108 %17
+         %17 = OpLabel
+        %107 = OpPhi %7 %111 %114
+         %23 = OpPhi %7 %13 %114
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %19 None
+               OpBranchConditional %6 %20 %19
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpLoopMerge %27 %25 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranch %116
+        %116 = OpLabel
+        %117 = OpPhi %7 %8 %25 %120 %116
+        %118 = OpPhi %7 %13 %25 %119 %116
+        %119 = OpISub %7 %118 %11
+        %120 = OpIAdd %7 %117 %9
+        %121 = OpSLessThan %5 %120 %10
+               OpLoopMerge %26 %116 None
+               OpBranchConditional %121 %116 %26
+         %26 = OpLabel
+        %115 = OpPhi %7 %119 %116
+               OpBranchConditional %6 %24 %27
+         %27 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     DifferentSignednessAndVectors) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %14 5
+         %17 = OpConstant %14 10
+         %18 = OpConstant %14 20
+         %19 = OpTypeVector %7 2
+         %20 = OpTypeVector %14 2
+         %21 = OpConstantComposite %19 %12 %8
+         %22 = OpConstantComposite %20 %17 %15
+         %23 = OpConstantComposite %19 %13 %12
+         %24 = OpConstantComposite %19 %11 %11
+          %2 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpBranch %30
+         %30 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // These tests check that the transformation is applicable and is applied
+  // correctly with integers, scalar and vectors, of different signedness.
+
+  // %12 is a signed integer, %18 and %16 are unsigned integers.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 18, 16, 10, 26, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %12 and %11 are signed integers, %18 is an unsigned integer.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 18, 11, 10, 27, 108, 109, 110, 111, 112, 113, 114, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(108, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %17, %18 and %16 are all signed integers.
+  auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym(
+      17, 18, 16, 10, 28, 115, 116, 117, 118, 119, 120, 121, 0);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(115, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %22 is an unsigned integer vector, %23 and %24 are signed integer vectors.
+  auto transformation4 = TransformationAddLoopToCreateIntConstantSynonym(
+      22, 23, 24, 10, 29, 122, 123, 124, 125, 126, 127, 128, 0);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  transformation4.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(22, {}), MakeDataDescriptor(122, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %21, %23 and %24 are all signed integer vectors.
+  auto transformation5 = TransformationAddLoopToCreateIntConstantSynonym(
+      21, 23, 24, 10, 30, 129, 130, 131, 132, 133, 134, 135, 0);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  transformation5.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(129, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %14 5
+         %17 = OpConstant %14 10
+         %18 = OpConstant %14 20
+         %19 = OpTypeVector %7 2
+         %20 = OpTypeVector %14 2
+         %21 = OpConstantComposite %19 %12 %8
+         %22 = OpConstantComposite %20 %17 %15
+         %23 = OpConstantComposite %19 %13 %12
+         %24 = OpConstantComposite %19 %11 %11
+          %2 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %25 %105 %101
+        %103 = OpPhi %14 %18 %25 %104 %101
+        %104 = OpISub %14 %103 %16
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %10
+               OpLoopMerge %26 %101 None
+               OpBranchConditional %106 %101 %26
+         %26 = OpLabel
+        %100 = OpPhi %14 %104 %101
+               OpBranch %109
+        %109 = OpLabel
+        %110 = OpPhi %7 %8 %26 %113 %109
+        %111 = OpPhi %14 %18 %26 %112 %109
+        %112 = OpISub %14 %111 %11
+        %113 = OpIAdd %7 %110 %9
+        %114 = OpSLessThan %5 %113 %10
+               OpLoopMerge %27 %109 None
+               OpBranchConditional %114 %109 %27
+         %27 = OpLabel
+        %108 = OpPhi %14 %112 %109
+               OpBranch %116
+        %116 = OpLabel
+        %117 = OpPhi %7 %8 %27 %120 %116
+        %118 = OpPhi %14 %18 %27 %119 %116
+        %119 = OpISub %14 %118 %16
+        %120 = OpIAdd %7 %117 %9
+        %121 = OpSLessThan %5 %120 %10
+               OpLoopMerge %28 %116 None
+               OpBranchConditional %121 %116 %28
+         %28 = OpLabel
+        %115 = OpPhi %14 %119 %116
+               OpBranch %123
+        %123 = OpLabel
+        %124 = OpPhi %7 %8 %28 %127 %123
+        %125 = OpPhi %19 %23 %28 %126 %123
+        %126 = OpISub %19 %125 %24
+        %127 = OpIAdd %7 %124 %9
+        %128 = OpSLessThan %5 %127 %10
+               OpLoopMerge %29 %123 None
+               OpBranchConditional %128 %123 %29
+         %29 = OpLabel
+        %122 = OpPhi %19 %126 %123
+               OpBranch %130
+        %130 = OpLabel
+        %131 = OpPhi %7 %8 %29 %134 %130
+        %132 = OpPhi %19 %23 %29 %133 %130
+        %133 = OpISub %19 %132 %24
+        %134 = OpIAdd %7 %131 %9
+        %135 = OpSLessThan %5 %134 %10
+               OpLoopMerge %30 %130 None
+               OpBranchConditional %135 %130 %30
+         %30 = OpLabel
+        %129 = OpPhi %19 %133 %130
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, 64BitConstants) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpTypeInt 64 1
+         %12 = OpConstant %11 5
+         %13 = OpConstant %11 10
+         %14 = OpConstant %11 20
+         %15 = OpTypeVector %11 2
+         %16 = OpConstantComposite %15 %13 %13
+         %17 = OpConstantComposite %15 %14 %14
+         %18 = OpConstantComposite %15 %12 %12
+          %2 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // These tests check that the transformation can be applied, and is applied
+  // correctly, to 64-bit integer (scalar and vector) constants.
+
+  // 64-bit scalar integers.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      13, 14, 12, 10, 20, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(13, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // 64-bit vector integers.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      16, 17, 18, 10, 21, 107, 108, 109, 110, 111, 112, 113, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(16, {}), MakeDataDescriptor(107, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpTypeInt 64 1
+         %12 = OpConstant %11 5
+         %13 = OpConstant %11 10
+         %14 = OpConstant %11 20
+         %15 = OpTypeVector %11 2
+         %16 = OpConstantComposite %15 %13 %13
+         %17 = OpConstantComposite %15 %14 %14
+         %18 = OpConstantComposite %15 %12 %12
+          %2 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %19 %105 %101
+        %103 = OpPhi %11 %14 %19 %104 %101
+        %104 = OpISub %11 %103 %12
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %10
+               OpLoopMerge %20 %101 None
+               OpBranchConditional %106 %101 %20
+         %20 = OpLabel
+        %100 = OpPhi %11 %104 %101
+               OpBranch %108
+        %108 = OpLabel
+        %109 = OpPhi %7 %8 %20 %112 %108
+        %110 = OpPhi %15 %17 %20 %111 %108
+        %111 = OpISub %15 %110 %18
+        %112 = OpIAdd %7 %109 %9
+        %113 = OpSLessThan %5 %112 %10
+               OpLoopMerge %21 %108 None
+               OpBranchConditional %113 %108 %21
+         %21 = OpLabel
+        %107 = OpPhi %15 %111 %108
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Underflow) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 20
+         %12 = OpConstant %7 -4
+         %13 = OpTypeInt 32 0
+         %14 = OpConstant %13 214748365
+         %15 = OpConstant %13 4294967256
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // These tests check that underflows are taken into consideration when
+  // deciding if  transformation is applicable.
+
+  // Subtracting 2147483648 20 times from 32-bit integer 0 underflows 2 times
+  // and the result is equivalent to -4.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 8, 14, 11, 17, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Subtracting 20 twice from 0 underflows and gives the unsigned integer
+  // 4294967256.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      15, 8, 11, 10, 18, 107, 108, 109, 110, 111, 112, 113, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(107, {})));
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 20
+         %12 = OpConstant %7 -4
+         %13 = OpTypeInt 32 0
+         %14 = OpConstant %13 214748365
+         %15 = OpConstant %13 4294967256
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %16 %105 %101
+        %103 = OpPhi %7 %8 %16 %104 %101
+        %104 = OpISub %7 %103 %14
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %11
+               OpLoopMerge %17 %101 None
+               OpBranchConditional %106 %101 %17
+         %17 = OpLabel
+        %100 = OpPhi %7 %104 %101
+               OpBranch %108
+        %108 = OpLabel
+        %109 = OpPhi %7 %8 %17 %112 %108
+        %110 = OpPhi %7 %8 %17 %111 %108
+        %111 = OpISub %7 %110 %11
+        %112 = OpIAdd %7 %109 %9
+        %113 = OpSLessThan %5 %112 %10
+               OpLoopMerge %18 %108 None
+               OpBranchConditional %113 %108 %18
+         %18 = OpLabel
+        %107 = OpPhi %7 %111 %108
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools