spirv-fuzz: add FuzzerPassAddCompositeInserts (#3606)

Adds FuzzerPassAddCompositeInserts, which randomly adds new
OpCompositeInsert instructions. Each OpCompositeInsert instruction
yields a copy of an original composite with one subcomponent replaced
with an existing or newly added object. Synonym facts are added for the
unchanged components in the original and added composite, and for the
replaced subcomponent and the object, if possible.

Fixes #2859
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 0a4df4e..db32902 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -38,6 +38,7 @@
         fuzzer_context.h
         fuzzer_pass.h
         fuzzer_pass_add_access_chains.h
+        fuzzer_pass_add_composite_inserts.h
         fuzzer_pass_add_composite_types.h
         fuzzer_pass_add_copy_memory.h
         fuzzer_pass_add_dead_blocks.h
@@ -131,6 +132,7 @@
         transformation_adjust_branch_weights.h
         transformation_composite_construct.h
         transformation_composite_extract.h
+        transformation_composite_insert.h
         transformation_compute_data_synonym_fact_closure.h
         transformation_context.h
         transformation_equation_instruction.h
@@ -178,6 +180,7 @@
         fuzzer_context.cpp
         fuzzer_pass.cpp
         fuzzer_pass_add_access_chains.cpp
+        fuzzer_pass_add_composite_inserts.cpp
         fuzzer_pass_add_composite_types.cpp
         fuzzer_pass_add_copy_memory.cpp
         fuzzer_pass_add_dead_blocks.cpp
@@ -270,6 +273,7 @@
         transformation_adjust_branch_weights.cpp
         transformation_composite_construct.cpp
         transformation_composite_extract.cpp
+        transformation_composite_insert.cpp
         transformation_compute_data_synonym_fact_closure.cpp
         transformation_context.cpp
         transformation_equation_instruction.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 77ef8e2..651db36 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -21,6 +21,7 @@
 #include "source/fuzz/fact_manager.h"
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass_add_access_chains.h"
+#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
 #include "source/fuzz/fuzzer_pass_add_composite_types.h"
 #include "source/fuzz/fuzzer_pass_add_copy_memory.h"
 #include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
@@ -35,6 +36,7 @@
 #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
 #include "source/fuzz/fuzzer_pass_add_parameters.h"
+#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
 #include "source/fuzz/fuzzer_pass_add_stores.h"
 #include "source/fuzz/fuzzer_pass_add_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h"
@@ -60,7 +62,11 @@
 #include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
 #include "source/fuzz/fuzzer_pass_propagate_instructions_up.h"
 #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
+#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
+#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
+#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
 #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
+#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
 #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
 #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
@@ -210,6 +216,9 @@
     MaybeAddPass<FuzzerPassAddAccessChains>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddCompositeInserts>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassAddCompositeTypes>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
@@ -249,6 +258,9 @@
     MaybeAddPass<FuzzerPassAddParameters>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddRelaxedDecorations>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassAddStores>(&passes, ir_context.get(),
                                       &transformation_context, &fuzzer_context,
                                       transformation_sequence_out);
@@ -300,6 +312,18 @@
     MaybeAddPass<FuzzerPassPushIdsThroughVariables>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
+    MaybeAddPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
+    MaybeAddPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
+    MaybeAddPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassReplaceParameterWithGlobal>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index ced25a5..a187daa 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -27,6 +27,7 @@
 const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
                                                                          90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
@@ -64,6 +65,8 @@
 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> kChanceOfDonatingAdditionalModule = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
+    30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
     {50, 95};
 const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
@@ -86,7 +89,7 @@
                                                                           70};
 const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
 const std::pair<uint32_t, uint32_t>
-    kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 90};
+    kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
     {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
@@ -153,6 +156,8 @@
       ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
   chance_of_adding_array_or_struct_type_ =
       ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
+  chance_of_adding_composite_insert_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert);
   chance_of_adding_copy_memory_ =
       ChooseBetweenMinAndMax(kChanceOfAddingCopyMemory);
   chance_of_adding_dead_block_ =
@@ -207,6 +212,8 @@
   chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
   chance_of_donating_additional_module_ =
       ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
+  chance_of_going_deeper_to_insert_in_composite_ =
+      ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
   chance_of_going_deeper_when_making_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
   chance_of_interchanging_signedness_of_integer_operands_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 947a834..c501614 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -115,6 +115,9 @@
   uint32_t GetChanceOfAddingArrayOrStructType() {
     return chance_of_adding_array_or_struct_type_;
   }
+  uint32_t GetChanceOfAddingCompositeInsert() {
+    return chance_of_adding_composite_insert_;
+  }
   uint32_t GetChanceOfAddingCopyMemory() {
     return chance_of_adding_copy_memory_;
   }
@@ -186,6 +189,9 @@
   uint32_t GetChanceOfDonatingAdditionalModule() {
     return chance_of_donating_additional_module_;
   }
+  uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
+    return chance_of_going_deeper_to_insert_in_composite_;
+  }
   uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
     return chance_of_going_deeper_when_making_access_chain_;
   }
@@ -296,6 +302,9 @@
   uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
     return random_generator_->RandomUint32(composite_size_bound);
   }
+  uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) {
+    return random_generator_->RandomUint32(number_of_components);
+  }
   uint32_t GetRandomLoopControlPartialCount() {
     return random_generator_->RandomUint32(max_loop_control_partial_count_);
   }
@@ -342,6 +351,7 @@
   uint32_t chance_of_adding_access_chain_;
   uint32_t chance_of_adding_another_struct_field_;
   uint32_t chance_of_adding_array_or_struct_type_;
+  uint32_t chance_of_adding_composite_insert_;
   uint32_t chance_of_adding_copy_memory_;
   uint32_t chance_of_adding_dead_block_;
   uint32_t chance_of_adding_dead_break_;
@@ -371,6 +381,7 @@
   uint32_t chance_of_constructing_composite_;
   uint32_t chance_of_copying_object_;
   uint32_t chance_of_donating_additional_module_;
+  uint32_t chance_of_going_deeper_to_insert_in_composite_;
   uint32_t chance_of_going_deeper_when_making_access_chain_;
   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 3c8c65b..3097bc7 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -518,6 +518,26 @@
   }
 }
 
+bool FuzzerPass::CanFindOrCreateZeroConstant(const opt::analysis::Type& type) {
+  switch (type.kind()) {
+    case opt::analysis::Type::kBool:
+    case opt::analysis::Type::kInteger:
+    case opt::analysis::Type::kFloat:
+    case opt::analysis::Type::kArray:
+    case opt::analysis::Type::kMatrix:
+    case opt::analysis::Type::kVector:
+      return true;
+    case opt::analysis::Type::kStruct:
+      return std::all_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [this](const opt::analysis::Type* element_type) {
+                           return CanFindOrCreateZeroConstant(*element_type);
+                         });
+    default:
+      return false;
+  }
+}
+
 void FuzzerPass::MaybeAddUseToReplace(
     opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
     std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 423b443..1e6992a 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -273,6 +273,9 @@
   uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id,
                                     bool is_irrelevant);
 
+  // Checks if FindOrCreateZeroConstant can be called on this type.
+  bool CanFindOrCreateZeroConstant(const opt::analysis::Type& type);
+
   // Adds a pair (id_use_descriptor, |replacement_id|) to the vector
   // |uses_to_replace|, where id_use_descriptor is the id use descriptor
   // representing the usage of an id in the |use_inst| instruction, at operand
diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
new file mode 100644
index 0000000..092a1d5
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
@@ -0,0 +1,236 @@
+// 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_composite_inserts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/transformation_composite_insert.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddCompositeInserts::~FuzzerPassAddCompositeInserts() = default;
+
+void FuzzerPassAddCompositeInserts::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator instruction_iterator,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        assert(instruction_iterator->opcode() ==
+                   instruction_descriptor.target_instruction_opcode() &&
+               "The opcode of the instruction we might insert before must be "
+               "the same as the opcode in the descriptor for the instruction");
+
+        // Randomly decide whether to try adding an OpCompositeInsert
+        // instruction.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingCompositeInsert())) {
+          return;
+        }
+
+        // It must be possible to insert an OpCompositeInsert instruction
+        // before |instruction_iterator|.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                SpvOpCompositeInsert, instruction_iterator)) {
+          return;
+        }
+
+        // Look for available values that have composite type.
+        std::vector<opt::Instruction*> available_composites =
+            FindAvailableInstructions(
+                function, block, instruction_iterator,
+                [instruction_descriptor](
+                    opt::IRContext* ir_context,
+                    opt::Instruction* instruction) -> bool {
+                  // |instruction| must be a supported instruction of composite
+                  // type.
+                  if (!TransformationCompositeInsert::
+                          IsCompositeInstructionSupported(ir_context,
+                                                          instruction)) {
+                    return false;
+                  }
+
+                  auto instruction_type = ir_context->get_type_mgr()->GetType(
+                      instruction->type_id());
+
+                  // No components of the composite can have type
+                  // OpTypeRuntimeArray.
+                  if (ContainsRuntimeArray(*instruction_type)) {
+                    return false;
+                  }
+
+                  // No components of the composite can be pointers.
+                  // TODO:
+                  // (https://github.com/KhronosGroup/SPIRV-Tools/issues/3658)
+                  //       Structs can have components of pointer type.
+                  //       FindOrCreateZeroConstant cannot be called on a
+                  //       pointer. We ignore pointers for now. Consider adding
+                  //       support for pointer types.
+                  if (ContainsPointer(*instruction_type)) {
+                    return false;
+                  }
+
+                  return true;
+                });
+
+        // If there are no available values, then return.
+        if (available_composites.empty()) {
+          return;
+        }
+
+        // Choose randomly one available composite value.
+        auto available_composite =
+            available_composites[GetFuzzerContext()->RandomIndex(
+                available_composites)];
+
+        // Take a random component of the chosen composite value. If the chosen
+        // component is itself a composite, then randomly decide whether to take
+        // its component and repeat.
+        uint32_t current_node_type_id = available_composite->type_id();
+        std::vector<uint32_t> path_to_replaced;
+        while (true) {
+          auto current_node_type_inst =
+              GetIRContext()->get_def_use_mgr()->GetDef(current_node_type_id);
+          uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
+              *current_node_type_inst, GetIRContext());
+
+          // If the composite is empty, then end the iteration.
+          if (num_of_components == 0) {
+            break;
+          }
+          uint32_t one_selected_index =
+              GetFuzzerContext()->GetRandomIndexForCompositeInsert(
+                  num_of_components);
+
+          // Construct a final index by appending the current index.
+          path_to_replaced.push_back(one_selected_index);
+          current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
+              GetIRContext(), current_node_type_id, one_selected_index);
+
+          // If the component is not a composite then end the iteration.
+          if (!fuzzerutil::IsCompositeType(
+                  GetIRContext()->get_type_mgr()->GetType(
+                      current_node_type_id))) {
+            break;
+          }
+
+          // If the component is a composite, but we decide not to go deeper,
+          // then end the iteration.
+          if (!GetFuzzerContext()->ChoosePercentage(
+                  GetFuzzerContext()
+                      ->GetChanceOfGoingDeeperToInsertInComposite())) {
+            break;
+          }
+        }
+
+        // Look for available objects that have the type id
+        // |current_node_type_id| and can be inserted.
+        std::vector<opt::Instruction*> available_objects =
+            FindAvailableInstructions(
+                function, block, instruction_iterator,
+                [instruction_descriptor, current_node_type_id](
+                    opt::IRContext* /*unused*/,
+                    opt::Instruction* instruction) -> bool {
+                  if (instruction->result_id() == 0 ||
+                      instruction->type_id() == 0) {
+                    return false;
+                  }
+                  if (instruction->type_id() != current_node_type_id) {
+                    return false;
+                  }
+                  return true;
+                });
+
+        // If there are no objects of the specific type available, check if
+        // FindOrCreateZeroConstant can be called and create a zero constant of
+        // this type.
+        uint32_t available_object_id;
+        if (available_objects.empty()) {
+          auto current_node_type =
+              GetIRContext()->get_type_mgr()->GetType(current_node_type_id);
+          if (!CanFindOrCreateZeroConstant(*current_node_type)) {
+            return;
+          }
+          available_object_id =
+              FindOrCreateZeroConstant(current_node_type_id, false);
+        } else {
+          available_object_id =
+              available_objects[GetFuzzerContext()->RandomIndex(
+                                    available_objects)]
+                  ->result_id();
+        }
+        auto new_result_id = GetFuzzerContext()->GetFreshId();
+
+        // Insert an OpCompositeInsert instruction which copies
+        // |available_composite| and in the copy inserts the object
+        // of type |available_object_id| at index |index_to_replace|.
+        ApplyTransformation(TransformationCompositeInsert(
+            instruction_descriptor, new_result_id,
+            available_composite->result_id(), available_object_id,
+            path_to_replaced));
+      });
+}
+
+bool FuzzerPassAddCompositeInserts::ContainsPointer(
+    const opt::analysis::Type& type) {
+  switch (type.kind()) {
+    case opt::analysis::Type::kPointer:
+      return true;
+    case opt::analysis::Type::kArray:
+      return ContainsPointer(*type.AsArray()->element_type());
+    case opt::analysis::Type::kMatrix:
+      return ContainsPointer(*type.AsMatrix()->element_type());
+    case opt::analysis::Type::kVector:
+      return ContainsPointer(*type.AsVector()->element_type());
+    case opt::analysis::Type::kStruct:
+      return std::any_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [](const opt::analysis::Type* element_type) {
+                           return ContainsPointer(*element_type);
+                         });
+    default:
+      return false;
+  }
+}
+
+bool FuzzerPassAddCompositeInserts::ContainsRuntimeArray(
+    const opt::analysis::Type& type) {
+  switch (type.kind()) {
+    case opt::analysis::Type::kRuntimeArray:
+      return true;
+    case opt::analysis::Type::kStruct:
+      // If any component of a struct is of type OpTypeRuntimeArray, return
+      // true.
+      return std::any_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [](const opt::analysis::Type* element_type) {
+                           return ContainsRuntimeArray(*element_type);
+                         });
+    default:
+      return false;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.h b/source/fuzz/fuzzer_pass_add_composite_inserts.h
new file mode 100644
index 0000000..665efc9
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_inserts.h
@@ -0,0 +1,45 @@
+// 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 SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H
+#define SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds new OpCompositeInsert instructions to
+// available values that have the composite type.
+class FuzzerPassAddCompositeInserts : public FuzzerPass {
+ public:
+  FuzzerPassAddCompositeInserts(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddCompositeInserts();
+  void Apply() override;
+
+  // Checks if any component of a composite is a pointer.
+  static bool ContainsPointer(const opt::analysis::Type& type);
+
+  // Checks if any component of a composite has type OpTypeRuntimeArray.
+  static bool ContainsRuntimeArray(const opt::analysis::Type& type);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H
diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
index d506de6..139dc6e 100644
--- a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
@@ -37,6 +37,7 @@
     ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default;
 
 void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
+  std::vector<opt::Instruction> instructions_for_transformation;
   for (auto& function : *GetIRContext()->module()) {
     for (auto& block : function) {
       for (auto& instruction : block) {
@@ -52,25 +53,27 @@
                 IsInstructionSuitable(GetIRContext(), instruction)) {
           continue;
         }
-
-        // Get the operand type id. We know that both operands have the same
-        // type.
-        uint32_t operand_type_id =
-            GetIRContext()
-                ->get_def_use_mgr()
-                ->GetDef(instruction.GetSingleWordInOperand(
-                    kArithmeticInstructionIndexLeftInOperand))
-                ->type_id();
-
-        // Ensure the required struct type exists. The struct type is based on
-        // the operand type.
-        FindOrCreateStructType({operand_type_id, operand_type_id});
-
-        ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
-            GetFuzzerContext()->GetFreshId(), instruction.result_id()));
+        instructions_for_transformation.push_back(instruction);
       }
     }
   }
+  for (auto& instruction : instructions_for_transformation) {
+    // Get the operand type id. We know that both operands have the same
+    // type.
+    uint32_t operand_type_id =
+        GetIRContext()
+            ->get_def_use_mgr()
+            ->GetDef(instruction.GetSingleWordInOperand(
+                kArithmeticInstructionIndexLeftInOperand))
+            ->type_id();
+
+    // Ensure the required struct type exists. The struct type is based on
+    // the operand type.
+    FindOrCreateStructType({operand_type_id, operand_type_id});
+
+    ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
+        GetFuzzerContext()->GetFreshId(), instruction.result_id()));
+  }
 }
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
index e21ea5e..4c455a7 100644
--- a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
+++ b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
@@ -66,7 +66,14 @@
                                       ? SpvStorageClassPrivate
                                       : SpvStorageClassFunction;
 
-    // Find or create a constant to initialize the variable from.
+    // Find or create a constant to initialize the variable from. The type of
+    // |instruction| must be such that the function FindOrCreateConstant can be
+    // called.
+    auto instruction_type =
+        GetIRContext()->get_type_mgr()->GetType(instruction->type_id());
+    if (!CanFindOrCreateZeroConstant(*instruction_type)) {
+      return;
+    }
     auto variable_initializer_id =
         FindOrCreateZeroConstant(instruction->type_id(), false);
 
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 9e5b30e..3e523c3 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1353,6 +1353,5 @@
 }
 
 }  // namespace fuzzerutil
-
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index d86e138..af709f8 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -498,7 +498,6 @@
                                                  SpvOp opcode);
 
 }  // namespace fuzzerutil
-
 }  // namespace fuzz
 }  // namespace spvtools
 
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 2aa113f..225aa4f 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -412,6 +412,7 @@
     TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65;
     TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
     TransformationPropagateInstructionUp propagate_instruction_up = 67;
+    TransformationCompositeInsert composite_insert = 68;
     // Add additional option using the next available number.
   }
 }
@@ -1021,6 +1022,29 @@
 
 }
 
+message TransformationCompositeInsert {
+
+  // A transformation that adds an instruction OpCompositeInsert which creates
+  // a new composite from an existing composite, with an element inserted.
+
+  // A descriptor for an instruction before which the new instruction
+  // OpCompositeInsert should be inserted.
+  InstructionDescriptor instruction_to_insert_before = 1;
+
+  // Result id of the inserted OpCompositeInsert instruction.
+  uint32 fresh_id = 2;
+
+  // Id of the composite used as the basis for the insertion.
+  uint32 composite_id = 3;
+
+  // Id of the object to be inserted.
+  uint32 object_id = 4;
+
+  // Indices that indicate which part of the composite should be inserted into.
+  repeated uint32 index = 5;
+
+}
+
 message TransformationComputeDataSynonymFactClosure {
 
   // A transformation that impacts the fact manager only, forcing a computation
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index c3f3bef..ec765b3 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -49,6 +49,7 @@
 #include "source/fuzz/transformation_adjust_branch_weights.h"
 #include "source/fuzz/transformation_composite_construct.h"
 #include "source/fuzz/transformation_composite_extract.h"
+#include "source/fuzz/transformation_composite_insert.h"
 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
 #include "source/fuzz/transformation_equation_instruction.h"
 #include "source/fuzz/transformation_function_call.h"
@@ -179,6 +180,9 @@
     case protobufs::Transformation::TransformationCase::kCompositeExtract:
       return MakeUnique<TransformationCompositeExtract>(
           message.composite_extract());
+    case protobufs::Transformation::TransformationCase::kCompositeInsert:
+      return MakeUnique<TransformationCompositeInsert>(
+          message.composite_insert());
     case protobufs::Transformation::TransformationCase::
         kComputeDataSynonymFactClosure:
       return MakeUnique<TransformationComputeDataSynonymFactClosure>(
diff --git a/source/fuzz/transformation_composite_insert.cpp b/source/fuzz/transformation_composite_insert.cpp
new file mode 100644
index 0000000..75eebd4
--- /dev/null
+++ b/source/fuzz/transformation_composite_insert.cpp
@@ -0,0 +1,226 @@
+// 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 "transformation_composite_insert.h"
+
+#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationCompositeInsert::TransformationCompositeInsert(
+    const spvtools::fuzz::protobufs::TransformationCompositeInsert& message)
+    : message_(message) {}
+
+TransformationCompositeInsert::TransformationCompositeInsert(
+    const protobufs::InstructionDescriptor& instruction_to_insert_before,
+    uint32_t fresh_id, uint32_t composite_id, uint32_t object_id,
+    const std::vector<uint32_t>& index) {
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+  message_.set_fresh_id(fresh_id);
+  message_.set_composite_id(composite_id);
+  message_.set_object_id(object_id);
+  for (auto an_index : index) {
+    message_.add_index(an_index);
+  }
+}
+
+bool TransformationCompositeInsert::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |message_.fresh_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // |message_.composite_id| must refer to an existing composite value.
+  auto composite =
+      ir_context->get_def_use_mgr()->GetDef(message_.composite_id());
+
+  if (!IsCompositeInstructionSupported(ir_context, composite)) {
+    return false;
+  }
+
+  // The indices in |message_.index| must be suitable for indexing into
+  // |composite->type_id()|.
+  auto component_to_be_replaced_type_id = fuzzerutil::WalkCompositeTypeIndices(
+      ir_context, composite->type_id(), message_.index());
+  if (component_to_be_replaced_type_id == 0) {
+    return false;
+  }
+
+  // The instruction having the id of |message_.object_id| must be defined.
+  auto object_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.object_id());
+  if (object_instruction == nullptr || object_instruction->type_id() == 0) {
+    return false;
+  }
+
+  // We ignore pointers for now.
+  auto object_instruction_type =
+      ir_context->get_type_mgr()->GetType(object_instruction->type_id());
+  if (object_instruction_type->AsPointer() != nullptr) {
+    return false;
+  }
+
+  // The type id of the object having |message_.object_id| and the type id of
+  // the component of the composite at index |message_.index| must be the same.
+  if (component_to_be_replaced_type_id != object_instruction->type_id()) {
+    return false;
+  }
+
+  // |message_.instruction_to_insert_before| must be a defined instruction.
+  auto instruction_to_insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  if (instruction_to_insert_before == nullptr) {
+    return false;
+  }
+
+  // |message_.composite_id| and |message_.object_id| must be available before
+  // the |message_.instruction_to_insert_before|.
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+          ir_context, instruction_to_insert_before, message_.composite_id())) {
+    return false;
+  }
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+          ir_context, instruction_to_insert_before, message_.object_id())) {
+    return false;
+  }
+
+  // It must be possible to insert an OpCompositeInsert before this
+  // instruction.
+  return fuzzerutil::CanInsertOpcodeBeforeInstruction(
+      SpvOpCompositeInsert, instruction_to_insert_before);
+}
+
+void TransformationCompositeInsert::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // |message_.struct_fresh_id| must be fresh.
+  assert(fuzzerutil::IsFreshId(ir_context, message_.fresh_id()) &&
+         "|message_.fresh_id| must be fresh");
+
+  std::vector<uint32_t> index =
+      fuzzerutil::RepeatedFieldToVector(message_.index());
+  opt::Instruction::OperandList in_operands;
+  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.object_id()}});
+  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.composite_id()}});
+  for (auto i : index) {
+    in_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}});
+  }
+  auto composite_type_id =
+      fuzzerutil::GetTypeId(ir_context, message_.composite_id());
+
+  FindInstruction(message_.instruction_to_insert_before(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpCompositeInsert, composite_type_id,
+          message_.fresh_id(), std::move(in_operands)));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // We have modified the module so most analyzes are now invalid.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Add facts about synonyms. Every element which hasn't been changed in
+  // the copy is synonymous to the corresponding element in the original
+  // composite which has id |message_.composite_id|. For every index that is a
+  // prefix of |index|, the components different from the one that
+  // contains the inserted object are synonymous with corresponding
+  // elements in the original composite.
+
+  // If |composite_id| is irrelevant then don't add any synonyms.
+  if (transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.composite_id())) {
+    return;
+  }
+  uint32_t current_node_type_id = composite_type_id;
+  std::vector<uint32_t> current_index;
+
+  for (uint32_t current_level = 0; current_level < index.size();
+       current_level++) {
+    auto current_node_type_inst =
+        ir_context->get_def_use_mgr()->GetDef(current_node_type_id);
+    uint32_t index_to_skip = index[current_level];
+    uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
+        *current_node_type_inst, ir_context);
+
+    // Update the current_node_type_id.
+    current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
+        ir_context, current_node_type_id, index_to_skip);
+
+    for (uint32_t i = 0; i < num_of_components; i++) {
+      if (i == index_to_skip) {
+        continue;
+      }
+      current_index.push_back(i);
+      // TODO: (https://github.com/KhronosGroup/SPIRV-Tools/issues/3659)
+      //       Google C++ guide restricts the use of r-value references.
+      //       https://google.github.io/styleguide/cppguide.html#Rvalue_references
+      //       Consider changing the signature of MakeDataDescriptor()
+      transformation_context->GetFactManager()->AddFactDataSynonym(
+          MakeDataDescriptor(message_.fresh_id(),
+                             std::vector<uint32_t>(current_index)),
+          MakeDataDescriptor(message_.composite_id(),
+                             std::vector<uint32_t>(current_index)),
+          ir_context);
+      current_index.pop_back();
+    }
+    // Store the prefix of the |index|.
+    current_index.push_back(index[current_level]);
+  }
+  // The element which has been changed is synonymous to the found object
+  // itself. Add this fact only if |object_id| is not irrelevant.
+  if (!transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.object_id())) {
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(message_.object_id(), {}),
+        MakeDataDescriptor(message_.fresh_id(), std::vector<uint32_t>(index)),
+        ir_context);
+  }
+}
+
+protobufs::Transformation TransformationCompositeInsert::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_composite_insert() = message_;
+  return result;
+}
+
+bool TransformationCompositeInsert::IsCompositeInstructionSupported(
+    opt::IRContext* ir_context, opt::Instruction* instruction) {
+  if (instruction == nullptr) {
+    return false;
+  }
+  if (instruction->result_id() == 0 || instruction->type_id() == 0) {
+    return false;
+  }
+  auto composite_type =
+      ir_context->get_type_mgr()->GetType(instruction->type_id());
+  if (!fuzzerutil::IsCompositeType(composite_type)) {
+    return false;
+  }
+
+  // Empty composites are not supported.
+  auto instruction_type_inst =
+      ir_context->get_def_use_mgr()->GetDef(instruction->type_id());
+  if (fuzzerutil::GetBoundForCompositeIndex(*instruction_type_inst,
+                                            ir_context) == 0) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_composite_insert.h b/source/fuzz/transformation_composite_insert.h
new file mode 100644
index 0000000..c4ea9e4
--- /dev/null
+++ b/source/fuzz/transformation_composite_insert.h
@@ -0,0 +1,72 @@
+// 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 SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H
+#define SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationCompositeInsert : public Transformation {
+ public:
+  explicit TransformationCompositeInsert(
+      const protobufs::TransformationCompositeInsert& message);
+
+  TransformationCompositeInsert(
+      const protobufs::InstructionDescriptor& instruction_to_insert_before,
+      uint32_t fresh_id, uint32_t composite_id, uint32_t object_id,
+      const std::vector<uint32_t>& index);
+
+  // - |message_.fresh_id| must be fresh.
+  // - |message_.composite_id| must refer to an existing composite value.
+  // - |message_.index| must refer to a correct index in the composite.
+  // - The type id of the object and the type id of the component of the
+  //   composite at index |message_.index| must be the same.
+  // - |message_.instruction_to_insert_before| must refer to a defined
+  //   instruction.
+  // - It must be possible to insert OpCompositeInsert before
+  //   |instruction_to_insert_before|.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction OpCompositeInsert before
+  // |instruction_to_insert_before|, which creates a new composite from
+  // |composite_id| by inserting |object_id| at the specified |index|.
+  // Synonyms are created between those components which are identical in the
+  // original and the modified composite and between the inserted object and its
+  // copy in the modified composite.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Checks if |instruction| is a instruction of a composite type supported by
+  // this transformation.
+  static bool IsCompositeInstructionSupported(opt::IRContext* ir_context,
+                                              opt::Instruction* instruction);
+
+ private:
+  protobufs::TransformationCompositeInsert message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_TRANSFORMATION_COMPOSITE_INSERT_H
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 7a28a57..3b64567 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -57,6 +57,7 @@
           transformation_adjust_branch_weights_test.cpp
           transformation_composite_construct_test.cpp
           transformation_composite_extract_test.cpp
+          transformation_composite_insert_test.cpp
           transformation_compute_data_synonym_fact_closure_test.cpp
           transformation_equation_instruction_test.cpp
           transformation_function_call_test.cpp
diff --git a/test/fuzz/transformation_composite_insert_test.cpp b/test/fuzz/transformation_composite_insert_test.cpp
new file mode 100644
index 0000000..de0cbe3
--- /dev/null
+++ b/test/fuzz/transformation_composite_insert_test.cpp
@@ -0,0 +1,814 @@
+// 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_composite_insert.h"
+
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationCompositeInsertTest, NotApplicableScenarios) {
+  // This test handles cases where IsApplicable() returns false.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpName %20 "l1"
+               OpName %24 "level_2"
+               OpMemberName %24 0 "c1"
+               OpMemberName %24 1 "c2"
+               OpName %26 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12
+         %19 = OpTypePointer Function %18
+         %24 = OpTypeStruct %18 %18
+         %25 = OpTypePointer Function %24
+         %30 = OpTypeBool
+         %31 = OpConstantTrue %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpCompositeConstruct %18 %21 %22
+               OpStore %20 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpCompositeConstruct %24 %27 %28
+               OpStore %26 %29
+               OpSelectionMerge %33 None
+               OpBranchConditional %31 %32 %33
+         %32 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: |fresh_id| is not fresh.
+  auto transformation_bad_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 20, 29, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |composite_id| does not refer to a existing instruction.
+  auto transformation_bad_2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 40, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |composite_id| does not refer to a composite value.
+  auto transformation_bad_3 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 9, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |object_id| does not refer to a defined instruction.
+  auto transformation_bad_4 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 40, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |object_id| cannot refer to a pointer.
+  auto transformation_bad_5 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 8, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_5.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |index| is not a correct index.
+  auto transformation_bad_6 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {2, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_6.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Type id of the object to be inserted and the type id of the
+  // component at |index| are not the same.
+  auto transformation_bad_7 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {1, 0});
+  ASSERT_FALSE(
+      transformation_bad_7.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |instruction_to_insert_before| does not refer to a defined
+  // instruction.
+  auto transformation_bad_8 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpIMul, 0), 50, 29, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_8.IsApplicable(context.get(), transformation_context));
+
+  // Bad: OpCompositeInsert cannot be inserted before OpBranchConditional with
+  // OpSelectionMerge above it.
+  auto transformation_bad_9 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpBranchConditional, 0), 50, 29, 11,
+      {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_9.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |composite_id| does not have a type_id.
+  auto transformation_bad_10 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 1, 11, {1, 0, 0});
+  ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(),
+                                                  transformation_context));
+}
+
+TEST(TransformationCompositeInsertTest, EmptyCompositeScenarios) {
+  // This test handles cases where either the composite is empty or the
+  // composite contains an empty composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+          %2 = OpTypeVoid
+         %60 = OpTypeStruct
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %61 = OpConstantComposite %60
+         %62 = OpConstantComposite %60
+         %12 = OpTypeStruct %6 %6
+         %63 = OpTypeStruct %6 %60
+         %13 = OpTypePointer Function %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+         %64 = OpCompositeConstruct %63 %15 %61
+               OpStore %14 %17
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: The composite with |composite_id| cannot be empty.
+  auto transformation_bad_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 61, 62, {1});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Good: It is possible to insert into a composite an element which is an
+  // empty composite.
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 64, 62, {1});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+          %2 = OpTypeVoid
+         %60 = OpTypeStruct
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %61 = OpConstantComposite %60
+         %62 = OpConstantComposite %60
+         %12 = OpTypeStruct %6 %6
+         %63 = OpTypeStruct %6 %60
+         %13 = OpTypePointer Function %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+         %64 = OpCompositeConstruct %63 %15 %61
+         %50 = OpCompositeInsert %63 %62 %64 1
+               OpStore %14 %17
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationCompositeInsertTest, IrrelevantCompositeNoSynonyms) {
+  // This test handles cases where either |composite| is irrelevant.
+  // The transformation shouldn't create any synonyms.
+  // The member composite has a different number of elements than the parent
+  // composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Add fact that the composite is irrelevant.
+  fact_manager.AddFactIdIsIrrelevant(30);
+
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // No synonyms should have been added.
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
+                                         MakeDataDescriptor(50, {0})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}),
+                                         MakeDataDescriptor(50, {1, 1})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}),
+                                         MakeDataDescriptor(50, {1, 2})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}),
+                                         MakeDataDescriptor(50, {1, 0, 1})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}),
+                                         MakeDataDescriptor(11, {})));
+}
+TEST(TransformationCompositeInsertTest, IrrelevantObjectSomeSynonyms) {
+  // This test handles cases where |object| is irrelevant.
+  // The transformation should create some synonyms. It shouldn't create a
+  // synonym related to |object|. The member composite has a different number of
+  // elements than the parent composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Add fact that the object is irrelevant.
+  fact_manager.AddFactIdIsIrrelevant(11);
+
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // These synonyms should have been added.
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
+                                        MakeDataDescriptor(50, {0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}),
+                                        MakeDataDescriptor(50, {1, 1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}),
+                                        MakeDataDescriptor(50, {1, 2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}),
+                                        MakeDataDescriptor(50, {1, 0, 1})));
+  // This synonym shouldn't have been added.
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}),
+                                         MakeDataDescriptor(11, {})));
+}
+
+TEST(TransformationCompositeInsertTest, ApplicableCreatedSynonyms) {
+  // This test handles cases where neither |composite| nor |object| is
+  // irrelevant. The transformation should create synonyms.
+  // The member composite has a different number of elements than the parent
+  // composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // These synonyms should have been added.
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
+                                        MakeDataDescriptor(50, {0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 1}),
+                                        MakeDataDescriptor(50, {1, 1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 2}),
+                                        MakeDataDescriptor(50, {1, 2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 1}),
+                                        MakeDataDescriptor(50, {1, 0, 1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1, 0, 0}),
+                                        MakeDataDescriptor(11, {})));
+
+  // These synonyms should not have been added.
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
+                                         MakeDataDescriptor(50, {1})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0}),
+                                         MakeDataDescriptor(50, {1, 0})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1, 0, 0}),
+                                         MakeDataDescriptor(50, {1, 0, 0})));
+
+  auto transformation_good_2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(50, SpvOpStore, 0), 51, 50, 11, {0, 1, 1});
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // These synonyms should have been added.
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {1}),
+                                        MakeDataDescriptor(51, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 0}),
+                                        MakeDataDescriptor(51, {0, 0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 2}),
+                                        MakeDataDescriptor(51, {0, 2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1, 0}),
+                                        MakeDataDescriptor(51, {0, 1, 0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(51, {0, 1, 1}),
+                                        MakeDataDescriptor(11, {})));
+
+  // These synonyms should not have been added.
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0}),
+                                         MakeDataDescriptor(51, {0})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1}),
+                                         MakeDataDescriptor(51, {0, 1})));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {0, 1, 1}),
+                                         MakeDataDescriptor(51, {0, 1, 1})));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+         %50 = OpCompositeInsert %25 %11 %30 1 0 0
+         %51 = OpCompositeInsert %25 %11 %50 0 1 1
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationCompositeInsertTest, IdNotAvailableScenarios) {
+  // This test handles cases where either the composite or the object is not
+  // available before the |instruction_to_insert_before|.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b1"
+               OpName %18 "b2"
+               OpName %22 "lvl1"
+               OpMemberName %22 0 "b1"
+               OpMemberName %22 1 "b2"
+               OpName %24 "l1"
+               OpName %28 "i3"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %22 = OpTypeStruct %12 %12
+         %23 = OpTypePointer Function %22
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %18 = OpVariable %13 Function
+         %24 = OpVariable %23 Function
+         %28 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %19 = OpLoad %6 %10
+         %20 = OpLoad %6 %8
+         %21 = OpCompositeConstruct %12 %19 %20
+               OpStore %18 %21
+         %25 = OpLoad %12 %14
+         %26 = OpLoad %12 %18
+         %27 = OpCompositeConstruct %22 %25 %26
+               OpStore %24 %27
+         %29 = OpLoad %6 %8
+         %30 = OpLoad %6 %10
+         %31 = OpIMul %6 %29 %30
+               OpStore %28 %31
+         %60 = OpCompositeConstruct %12 %20 %19
+         %61 = OpCompositeConstruct %22 %26 %25
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: The object with |object_id| is not available at
+  // |instruction_to_insert_before|.
+  auto transformation_bad_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 27, 60, {1});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The composite with |composite_id| is not available at
+  // |instruction_to_insert_before|.
+  auto transformation_bad_2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 61, 21, {1});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The |instruction_to_insert_before| is the composite itself and is
+  // available.
+  auto transformation_bad_3 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(61, SpvOpCompositeConstruct, 0), 50, 61, 21,
+      {1});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The |instruction_to_insert_before| is the object itself and is not
+  // available.
+  auto transformation_bad_4 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(60, SpvOpCompositeConstruct, 0), 50, 27, 60,
+      {1});
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools