Add 'copy object' transformation (#2766)

This transformation can introduce an instruction that uses
OpCopyObject to make a copy of some other result id.  This change
introduces the transformation, but does not yet introduce a fuzzer
pass to actually apply it.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index fbabba1..49ee843 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -26,6 +26,7 @@
   )
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
+        data_descriptor.h
         fact_manager.h
         fuzzer.h
         fuzzer_context.h
@@ -52,6 +53,7 @@
         transformation_add_type_float.h
         transformation_add_type_int.h
         transformation_add_type_pointer.h
+        transformation_copy_object.h
         transformation_move_block_down.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
@@ -59,6 +61,7 @@
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
+        data_descriptor.cpp
         fact_manager.cpp
         fuzzer.cpp
         fuzzer_context.cpp
@@ -84,6 +87,7 @@
         transformation_add_type_float.cpp
         transformation_add_type_int.cpp
         transformation_add_type_pointer.cpp
+        transformation_copy_object.cpp
         transformation_move_block_down.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
diff --git a/source/fuzz/data_descriptor.cpp b/source/fuzz/data_descriptor.cpp
new file mode 100644
index 0000000..9cdb2c5
--- /dev/null
+++ b/source/fuzz/data_descriptor.cpp
@@ -0,0 +1,42 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/data_descriptor.h"
+
+#include <algorithm>
+
+namespace spvtools {
+namespace fuzz {
+
+protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
+                                             std::vector<uint32_t>&& indices) {
+  protobufs::DataDescriptor result;
+  result.set_object(object);
+  for (auto index : indices) {
+    result.add_index(index);
+  }
+  return result;
+}
+
+bool DataDescriptorEquals::operator()(
+    const protobufs::DataDescriptor* first,
+    const protobufs::DataDescriptor* second) const {
+  return first->object() == second->object() &&
+         first->index().size() == second->index().size() &&
+         std::equal(first->index().begin(), first->index().end(),
+                    second->index().begin());
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/data_descriptor.h b/source/fuzz/data_descriptor.h
new file mode 100644
index 0000000..731bd21
--- /dev/null
+++ b/source/fuzz/data_descriptor.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_DATA_DESCRIPTOR_H_
+#define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
+#include <vector>
+
+namespace spvtools {
+namespace fuzz {
+
+// Factory method to create a data descriptor message from an object id and a
+// list of indices.
+protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
+                                             std::vector<uint32_t>&& indices);
+
+// Equality function for data descriptors.
+struct DataDescriptorEquals {
+  bool operator()(const protobufs::DataDescriptor* first,
+                  const protobufs::DataDescriptor* second) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
index 442ff16..61daa64 100644
--- a/source/fuzz/fact_manager.cpp
+++ b/source/fuzz/fact_manager.cpp
@@ -68,6 +68,9 @@
 
 }  // namespace
 
+//=======================
+// Constant uniform facts
+
 // The purpose of this struct is to group the fields and data used to represent
 // facts about uniform constants.
 struct FactManager::ConstantUniformFacts {
@@ -330,10 +333,44 @@
   return true;
 }
 
-FactManager::FactManager() {
-  uniform_constant_facts_ = MakeUnique<ConstantUniformFacts>();
+// End of uniform constant facts
+//==============================
+
+//==============================
+// Id synonym facts
+
+// The purpose of this struct is to group the fields and data used to represent
+// facts about id synonyms.
+struct FactManager::IdSynonymFacts {
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactIdSynonym& fact);
+
+  // A record of all the synonyms that are available.
+  std::map<uint32_t, std::vector<protobufs::DataDescriptor>> synonyms;
+
+  // The set of keys to the above map; useful if you just want to know which ids
+  // have synonyms.
+  std::set<uint32_t> ids_with_synonyms;
+};
+
+void FactManager::IdSynonymFacts::AddFact(
+    const protobufs::FactIdSynonym& fact) {
+  if (synonyms.count(fact.id()) == 0) {
+    assert(ids_with_synonyms.count(fact.id()) == 0);
+    ids_with_synonyms.insert(fact.id());
+    synonyms[fact.id()] = std::vector<protobufs::DataDescriptor>();
+  }
+  assert(ids_with_synonyms.count(fact.id()) == 1);
+  synonyms[fact.id()].push_back(fact.data_descriptor());
 }
 
+// End of id synonym facts
+//==============================
+
+FactManager::FactManager()
+    : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()),
+      id_synonym_facts_(MakeUnique<IdSynonymFacts>()) {}
+
 FactManager::~FactManager() = default;
 
 void FactManager::AddFacts(const MessageConsumer& message_consumer,
@@ -350,13 +387,17 @@
 
 bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact& fact,
                           spvtools::opt::IRContext* context) {
-  assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
-         "Right now this is the only fact.");
-  if (!uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
-                                        context)) {
-    return false;
+  switch (fact.fact_case()) {
+    case protobufs::Fact::kConstantUniformFact:
+      return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
+                                              context);
+    case protobufs::Fact::kIdSynonymFact:
+      id_synonym_facts_->AddFact(fact.id_synonym_fact());
+      return true;
+    default:
+      assert(false && "Unknown fact type.");
+      return false;
   }
-  return true;
 }
 
 std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
@@ -389,5 +430,14 @@
   return uniform_constant_facts_->facts_and_type_ids;
 }
 
+const std::set<uint32_t>& FactManager::GetIdsForWhichSynonymsAreKnown() const {
+  return id_synonym_facts_->ids_with_synonyms;
+}
+
+const std::vector<protobufs::DataDescriptor>& FactManager::GetSynonymsForId(
+    uint32_t id) const {
+  return id_synonym_facts_->synonyms.at(id);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager.h
index cb4ac58..f6ea247 100644
--- a/source/fuzz/fact_manager.h
+++ b/source/fuzz/fact_manager.h
@@ -16,6 +16,7 @@
 #define SOURCE_FUZZ_FACT_MANAGER_H_
 
 #include <memory>
+#include <set>
 #include <utility>
 #include <vector>
 
@@ -51,13 +52,12 @@
   // fact manager.
   bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
 
-  // The fact manager will ultimately be responsible for managing a few distinct
-  // categories of facts. In principle there could be different fact managers
-  // for each kind of fact, but in practice providing one 'go to' place for
-  // facts will be convenient.  To keep some separation, the public methods of
-  // the fact manager should be grouped according to the kind of fact to which
-  // they relate.  At present we only have one kind of fact: facts about
-  // uniform variables.
+  // The fact manager is responsible for managing a few distinct categories of
+  // facts. In principle there could be different fact managers for each kind
+  // of fact, but in practice providing one 'go to' place for facts is
+  // convenient.  To keep some separation, the public methods of the fact
+  // manager should be grouped according to the kind of fact to which they
+  // relate.
 
   //==============================
   // Querying facts about uniform constants
@@ -96,6 +96,21 @@
   // End of uniform constant facts
   //==============================
 
+  //==============================
+  // Querying facts about id synonyms
+
+  // Returns every id for which a fact of the form "this id is synonymous
+  // with this piece of data" is known.
+  const std::set<uint32_t>& GetIdsForWhichSynonymsAreKnown() const;
+
+  // Requires that at least one synonym for |id| is known, and returns the
+  // sequence of all known synonyms.
+  const std::vector<protobufs::DataDescriptor>& GetSynonymsForId(
+      uint32_t id) const;
+
+  // End of id synonym facts
+  //==============================
+
  private:
   // For each distinct kind of fact to be managed, we use a separate opaque
   // struct type.
@@ -104,6 +119,10 @@
                                 // buffer elements.
   std::unique_ptr<ConstantUniformFacts>
       uniform_constant_facts_;  // Unique pointer to internal data.
+
+  struct IdSynonymFacts;  // Opaque struct for holding data about id synonyms.
+  std::unique_ptr<IdSynonymFacts>
+      id_synonym_facts_;  // Unique pointer to internal data.
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 9a05c74..9972e47 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -170,6 +170,41 @@
   return false;
 }
 
+opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
+    opt::BasicBlock* block, const opt::Instruction* base_inst,
+    uint32_t offset) {
+  // The cases where |base_inst| is the block's label, vs. inside the block,
+  // are dealt with separately.
+  if (base_inst == block->GetLabelInst()) {
+    // |base_inst| is the block's label.
+    if (offset == 0) {
+      // We cannot return an iterator to the block's label.
+      return block->end();
+    }
+    // Conceptually, the first instruction in the block is [label + 1].
+    // We thus start from 1 when applying the offset.
+    auto inst_it = block->begin();
+    for (uint32_t i = 1; i < offset && inst_it != block->end(); i++) {
+      ++inst_it;
+    }
+    // This is either the desired instruction, or the end of the block.
+    return inst_it;
+  }
+  // |base_inst| is inside the block.
+  for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
+    if (base_inst == &*inst_it) {
+      // We have found the base instruction; we now apply the offset.
+      for (uint32_t i = 0; i < offset && inst_it != block->end(); i++) {
+        ++inst_it;
+      }
+      // This is either the desired instruction, or the end of the block.
+      return inst_it;
+    }
+  }
+  assert(false && "The base instruction was not found.");
+  return nullptr;
+}
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 15228de..47588b0 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -18,6 +18,8 @@
 #include <vector>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/basic_block.h"
+#include "source/opt/instruction.h"
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -62,6 +64,16 @@
 bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
                                     uint32_t maybe_loop_header_id);
 
+// Requires that |base_inst| is either the label instruction of |block| or an
+// instruction inside |block|.
+//
+// If the block contains a (non-label, non-terminator) instruction |offset|
+// instructions after |base_inst|, an iterator to this instruction is returned.
+//
+// Otherwise |block|->end() is returned.
+opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset(
+    opt::BasicBlock* block, const opt::Instruction* base_inst, uint32_t offset);
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 13d8a05..4e8dcac 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -57,6 +57,22 @@
 
 }
 
+message DataDescriptor {
+
+  // Represents a data element that can be accessed from an id, by walking the
+  // type hierarchy via a sequence of 0 or more indices.
+  //
+  // Very similar to a UniformBufferElementDescriptor, except that a
+  // DataDescriptor is rooted at the id of a scalar or composite.
+
+  // The object being accessed - a scalar or composite
+  uint32 object = 1;
+
+  // 0 or more indices, used to index into a composite object
+  repeated uint32 index = 2;
+
+}
+
 message UniformBufferElementDescriptor {
 
   // Represents a data element inside a uniform buffer.  The element is
@@ -97,6 +113,7 @@
   oneof fact {
     // Order the fact options by numeric id (rather than alphabetically).
     FactConstantUniform constant_uniform_fact = 1;
+    FactIdSynonym id_synonym_fact = 2;
   }
 }
 
@@ -118,6 +135,22 @@
 
 }
 
+message FactIdSynonym {
+
+  // Records the fact that the data held in an id is guaranteed to be equal to
+  // the data held in a data descriptor.  spirv-fuzz can use this to replace
+  // uses of the id with references to the data described by the data
+  // descriptor.
+
+  // An id
+  uint32 id = 1;
+
+  // A data descriptor guaranteed to hold a value identical to that held by the
+  // id
+  DataDescriptor data_descriptor = 2;
+
+}
+
 message TransformationSequence {
   repeated Transformation transformation = 1;
 }
@@ -138,6 +171,7 @@
     TransformationAddTypePointer add_type_pointer = 10;
     TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
     TransformationAddDeadContinue add_dead_continue = 12;
+    TransformationCopyObject copy_object = 13;
     // Add additional option using the next available number.
   }
 }
@@ -262,6 +296,26 @@
 
 }
 
+message TransformationCopyObject {
+
+  // A transformation that introduces an OpCopyObject instruction to make a
+  // copy of an object.
+
+  // Id of the object to be copied
+  uint32 object = 1;
+
+  // The id of an instruction in a block
+  uint32 base_instruction_id = 2;
+
+  // An offset, such that OpCopyObject instruction should be inserted right
+  // before the instruction |offset| instructions after |base_instruction_id|
+  uint32 offset = 3;
+
+  // A fresh id for the copied object
+  uint32 fresh_id = 4;
+
+}
+
 message TransformationMoveBlockDown {
 
   // A transformation that moves a basic block to be one position lower in
@@ -291,6 +345,7 @@
 }
 
 message TransformationReplaceBooleanConstantWithConstantBinary {
+
   // A transformation to capture replacing a use of a boolean constant with
   // binary operation on two constant values
 
@@ -313,13 +368,14 @@
 
 message TransformationSplitBlock {
 
-  // A transformation that splits a basic block into two basic blocks.
+  // A transformation that splits a basic block into two basic blocks
 
-  // The result id of an instruction.
-  uint32 result_id = 1;
+  // The result id of an instruction
+  uint32 base_instruction_id = 1;
 
-  // An offset, such that the block containing |result_id_| should be split
-  // right before the instruction |offset_| instructions after |result_id_|.
+  // An offset, such that the block containing |base_instruction_id| should be
+  // split right before the instruction |offset| instructions after
+  // |base_instruction_id|
   uint32 offset = 2;
 
   // An id that must not yet be used by the module to which this transformation
diff --git a/source/fuzz/transformation_copy_object.cpp b/source/fuzz/transformation_copy_object.cpp
new file mode 100644
index 0000000..f9ead43
--- /dev/null
+++ b/source/fuzz/transformation_copy_object.cpp
@@ -0,0 +1,158 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_copy_object.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/instruction.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationCopyObject::TransformationCopyObject(
+    const protobufs::TransformationCopyObject& message)
+    : message_(message) {}
+
+TransformationCopyObject::TransformationCopyObject(uint32_t object,
+                                                   uint32_t base_instruction_id,
+                                                   uint32_t offset,
+                                                   uint32_t fresh_id) {
+  message_.set_object(object);
+  message_.set_base_instruction_id(base_instruction_id);
+  message_.set_offset(offset);
+  message_.set_fresh_id(fresh_id);
+}
+
+bool TransformationCopyObject::IsApplicable(
+    opt::IRContext* context, const FactManager& /*fact_manager*/) const {
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    // We require the id for the object copy to be unused.
+    return false;
+  }
+  // The id of the object to be copied must exist
+  auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
+  if (!object_inst) {
+    return false;
+  }
+  if (!object_inst->type_id()) {
+    // We can only apply OpCopyObject to instructions that have types.
+    return false;
+  }
+  if (!context->get_decoration_mgr()
+           ->GetDecorationsFor(message_.object(), true)
+           .empty()) {
+    // We do not copy objects that have decorations: if the copy is not
+    // decorated analogously, using the original object vs. its copy may not be
+    // equivalent.
+    // TODO(afd): it would be possible to make the copy but not add an id
+    // synonym.
+    return false;
+  }
+
+  auto base_instruction =
+      context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
+  if (!base_instruction) {
+    // The given id to insert after is not defined.
+    return false;
+  }
+
+  auto destination_block = context->get_instr_block(base_instruction);
+  if (!destination_block) {
+    // The given id to insert after is not in a block.
+    return false;
+  }
+
+  auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
+      destination_block, base_instruction, message_.offset());
+
+  if (insert_before == destination_block->end()) {
+    // The offset was inappropriate.
+    return false;
+  }
+  if (insert_before->PreviousNode() &&
+      (insert_before->PreviousNode()->opcode() == SpvOpLoopMerge ||
+       insert_before->PreviousNode()->opcode() == SpvOpSelectionMerge)) {
+    // We cannot insert a copy directly after a merge instruction.
+    return false;
+  }
+  if (insert_before->opcode() == SpvOpVariable) {
+    // We cannot insert a copy directly before a variable; variables in a
+    // function must be contiguous in the entry block.
+    return false;
+  }
+  // We cannot insert a copy directly before OpPhi, because OpPhi instructions
+  // need to be contiguous at the start of a block.
+  if (insert_before->opcode() == SpvOpPhi) {
+    return false;
+  }
+  // |message_object| must be available at the point where we want to add the
+  // copy. It is available if it is at global scope (in which case it has no
+  // block), or if it dominates the point of insertion but is different from the
+  // point of insertion.
+  //
+  // The reason why the object needs to be different from the insertion point is
+  // that the copy will be added *before* this point, and we do not want to
+  // insert it before the object's defining instruction.
+  return !context->get_instr_block(object_inst) ||
+         (object_inst != &*insert_before &&
+          context->GetDominatorAnalysis(destination_block->GetParent())
+              ->Dominates(object_inst, &*insert_before));
+}
+
+void TransformationCopyObject::Apply(opt::IRContext* context,
+                                     FactManager* fact_manager) const {
+  // - A new instruction,
+  //     %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
+  //   is added directly before the instruction at |message_.insert_after_id| +
+  //   |message_|.offset, where %ty is the type of |message_.object|.
+  // - The fact that |message_.fresh_id| and |message_.object| are synonyms
+  //   is added to the fact manager.
+  // The id of the object to be copied must exist
+  auto object_inst = context->get_def_use_mgr()->GetDef(message_.object());
+  assert(object_inst && "The object to be copied must exist.");
+  auto base_instruction =
+      context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
+  assert(base_instruction && "The base instruction must exist.");
+  auto destination_block = context->get_instr_block(base_instruction);
+  assert(destination_block && "The base instruction must be in a block.");
+  auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
+      destination_block, base_instruction, message_.offset());
+  assert(insert_before != destination_block->end() &&
+         "There must be an instruction before which the copy can be inserted.");
+
+  opt::Instruction::OperandList operands = {
+      {SPV_OPERAND_TYPE_ID, {message_.object()}}};
+  insert_before->InsertBefore(MakeUnique<opt::Instruction>(
+      context, SpvOp::SpvOpCopyObject, object_inst->type_id(),
+      message_.fresh_id(), operands));
+
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+
+  protobufs::Fact fact;
+  fact.mutable_id_synonym_fact()->set_id(message_.object());
+  fact.mutable_id_synonym_fact()->mutable_data_descriptor()->set_object(
+      message_.fresh_id());
+  fact_manager->AddFact(fact, context);
+}
+
+protobufs::Transformation TransformationCopyObject::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_copy_object() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_copy_object.h b/source/fuzz/transformation_copy_object.h
new file mode 100644
index 0000000..6ce72df
--- /dev/null
+++ b/source/fuzz/transformation_copy_object.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationCopyObject : public Transformation {
+ public:
+  explicit TransformationCopyObject(
+      const protobufs::TransformationCopyObject& message);
+
+  TransformationCopyObject(uint32_t fresh_id, uint32_t object,
+                           uint32_t insert_after_id, uint32_t offset);
+
+  // - |message_.fresh_id| must not be used by the module.
+  // - |message_.object| must be a result id that is a legitimate operand for
+  //   OpCopyObject.  In particular, it must be the id of an instruction that
+  //   has a result type
+  // - |message_.object| must not be the target of any decoration.
+  //   TODO(afd): consider copying decorations along with objects.
+  // - |message_.insert_after_id| must be the result id of an instruction
+  //   'base' in some block 'blk'.
+  // - 'blk' must contain an instruction 'inst' located |message_.offset|
+  //   instructions after 'base' (if |message_.offset| = 0 then 'inst' =
+  //   'base').
+  // - It must be legal to insert an OpCopyObject instruction directly
+  //   before 'inst'.
+  // - |message_object| must be available directly before 'inst'.
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - A new instruction,
+  //     %|message_.fresh_id| = OpCopyObject %ty %|message_.object|
+  //   is added directly before the instruction at |message_.insert_after_id| +
+  //   |message_|.offset, where %ty is the type of |message_.object|.
+  // - The fact that |message_.fresh_id| and |message_.object| are synonyms
+  //   is added to the fact manager.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationCopyObject message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
index a8c33de..a2da371 100644
--- a/source/fuzz/transformation_split_block.cpp
+++ b/source/fuzz/transformation_split_block.cpp
@@ -26,147 +26,104 @@
     const spvtools::fuzz::protobufs::TransformationSplitBlock& message)
     : message_(message) {}
 
-TransformationSplitBlock::TransformationSplitBlock(uint32_t result_id,
+TransformationSplitBlock::TransformationSplitBlock(uint32_t base_instruction_id,
                                                    uint32_t offset,
                                                    uint32_t fresh_id) {
-  message_.set_result_id(result_id);
+  message_.set_base_instruction_id(base_instruction_id);
   message_.set_offset(offset);
   message_.set_fresh_id(fresh_id);
 }
 
-std::pair<bool, opt::BasicBlock::iterator>
-TransformationSplitBlock::FindInstToSplitBefore(opt::BasicBlock* block) const {
-  // There are three possibilities:
-  // (1) the transformation wants to split at some offset from the block's
-  //     label.
-  // (2) the transformation wants to split at some offset from a
-  //     non-label instruction inside the block.
-  // (3) the split assocaiated with this transformation has nothing to do with
-  //     this block
-  if (message_.result_id() == block->id()) {
-    // Case (1).
-    if (message_.offset() == 0) {
-      // The offset is not allowed to be 0: this would mean splitting before the
-      // block's label.
-      // By returning (true, block->end()), we indicate that we did find the
-      // instruction (so that it is not worth searching further for it), but
-      // that splitting will not be possible.
-      return {true, block->end()};
-    }
-    // Conceptually, the first instruction in the block is [label + 1].
-    // We thus start from 1 when applying the offset.
-    auto inst_it = block->begin();
-    for (uint32_t i = 1; i < message_.offset() && inst_it != block->end();
-         i++) {
-      ++inst_it;
-    }
-    // This is either the desired instruction, or the end of the block.
-    return {true, inst_it};
-  }
-  for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
-    if (message_.result_id() == inst_it->result_id()) {
-      // Case (2): we have found the base instruction; we now apply the offset.
-      for (uint32_t i = 0; i < message_.offset() && inst_it != block->end();
-           i++) {
-        ++inst_it;
-      }
-      // This is either the desired instruction, or the end of the block.
-      return {true, inst_it};
-    }
-  }
-  // Case (3).
-  return {false, block->end()};
-}
-
 bool TransformationSplitBlock::IsApplicable(
     opt::IRContext* context, const FactManager& /*unused*/) const {
   if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
     // We require the id for the new block to be unused.
     return false;
   }
-  // Consider every block in every function.
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
-      auto maybe_split_before = FindInstToSplitBefore(&block);
-      if (!maybe_split_before.first) {
-        continue;
-      }
-      if (maybe_split_before.second == block.end()) {
-        // The base instruction was found, but the offset was inappropriate.
-        return false;
-      }
-      if (block.IsLoopHeader()) {
-        // We cannot split a loop header block: back-edges would become invalid.
-        return false;
-      }
-      auto split_before = maybe_split_before.second;
-      if (split_before->PreviousNode() &&
-          split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
-        // We cannot split directly after a selection merge: this would separate
-        // the merge from its associated branch or switch operation.
-        return false;
-      }
-      if (split_before->opcode() == SpvOpVariable) {
-        // We cannot split directly after a variable; variables in a function
-        // must be contiguous in the entry block.
-        return false;
-      }
-      if (split_before->opcode() == SpvOpPhi &&
-          split_before->NumInOperands() != 2) {
-        // We cannot split before an OpPhi unless the OpPhi has exactly one
-        // associated incoming edge.
-        return false;
-      }
-      return true;
-    }
+  auto base_instruction =
+      context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
+  if (!base_instruction) {
+    // The instruction describing the block we should split does not exist.
+    return false;
   }
-  return false;
+  auto block_containing_base_instruction =
+      context->get_instr_block(base_instruction);
+  if (!block_containing_base_instruction) {
+    // The instruction describing the block we should split is not contained in
+    // a block.
+    return false;
+  }
+
+  if (block_containing_base_instruction->IsLoopHeader()) {
+    // We cannot split a loop header block: back-edges would become invalid.
+    return false;
+  }
+
+  auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
+      block_containing_base_instruction, base_instruction, message_.offset());
+  if (split_before == block_containing_base_instruction->end()) {
+    // The offset was inappropriate.
+    return false;
+  }
+  if (split_before->PreviousNode() &&
+      split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
+    // We cannot split directly after a selection merge: this would separate
+    // the merge from its associated branch or switch operation.
+    return false;
+  }
+  if (split_before->opcode() == SpvOpVariable) {
+    // We cannot split directly after a variable; variables in a function
+    // must be contiguous in the entry block.
+    return false;
+  }
+  // We cannot split before an OpPhi unless the OpPhi has exactly one
+  // associated incoming edge.
+  return !(split_before->opcode() == SpvOpPhi &&
+           split_before->NumInOperands() != 2);
 }
 
 void TransformationSplitBlock::Apply(opt::IRContext* context,
                                      FactManager* /*unused*/) const {
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
-      auto maybe_split_before = FindInstToSplitBefore(&block);
-      if (!maybe_split_before.first) {
-        continue;
-      }
-      assert(maybe_split_before.second != block.end() &&
-             "If the transformation is applicable, we should have an "
-             "instruction to split on.");
-      // We need to make sure the module's id bound is large enough to add the
-      // fresh id.
-      fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
-      // Split the block.
-      auto new_bb = block.SplitBasicBlock(context, message_.fresh_id(),
-                                          maybe_split_before.second);
-      // The split does not automatically add a branch between the two parts of
-      // the original block, so we add one.
-      block.AddInstruction(MakeUnique<opt::Instruction>(
+  auto base_instruction =
+      context->get_def_use_mgr()->GetDef(message_.base_instruction_id());
+  assert(base_instruction && "Base instruction must exist");
+  auto block_containing_base_instruction =
+      context->get_instr_block(base_instruction);
+  assert(block_containing_base_instruction &&
+         "Base instruction must be in a block");
+  auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset(
+      block_containing_base_instruction, base_instruction, message_.offset());
+  assert(split_before != block_containing_base_instruction->end() &&
+         "If the transformation is applicable, we should have an "
+         "instruction to split on.");
+  // We need to make sure the module's id bound is large enough to add the
+  // fresh id.
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  // Split the block.
+  auto new_bb = block_containing_base_instruction->SplitBasicBlock(
+      context, message_.fresh_id(), split_before);
+  // The split does not automatically add a branch between the two parts of
+  // the original block, so we add one.
+  block_containing_base_instruction->AddInstruction(
+      MakeUnique<opt::Instruction>(
           context, SpvOpBranch, 0, 0,
           std::initializer_list<opt::Operand>{
               opt::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID,
                            {message_.fresh_id()})}));
-      // If we split before OpPhi instructions, we need to update their
-      // predecessor operand so that the block they used to be inside is now the
-      // predecessor.
-      new_bb->ForEachPhiInst([&block](opt::Instruction* phi_inst) {
+  // If we split before OpPhi instructions, we need to update their
+  // predecessor operand so that the block they used to be inside is now the
+  // predecessor.
+  new_bb->ForEachPhiInst(
+      [block_containing_base_instruction](opt::Instruction* phi_inst) {
         // The following assertion is a sanity check.  It is guaranteed to hold
         // if IsApplicable holds.
         assert(phi_inst->NumInOperands() == 2 &&
                "We can only split a block before an OpPhi if block has exactly "
                "one predecessor.");
-        phi_inst->SetInOperand(1, {block.id()});
+        phi_inst->SetInOperand(1, {block_containing_base_instruction->id()});
       });
-      // Invalidate all analyses
-      context->InvalidateAnalysesExceptFor(
-          opt::IRContext::Analysis::kAnalysisNone);
-      return;
-    }
-  }
-  assert(0 &&
-         "Should be unreachable: it should have been possible to apply this "
-         "transformation.");
+  // Invalidate all analyses
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationSplitBlock::ToMessage() const {
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
index ef4aa75..4a7095a 100644
--- a/source/fuzz/transformation_split_block.h
+++ b/source/fuzz/transformation_split_block.h
@@ -28,13 +28,13 @@
   explicit TransformationSplitBlock(
       const protobufs::TransformationSplitBlock& message);
 
-  TransformationSplitBlock(uint32_t result_id, uint32_t offset,
+  TransformationSplitBlock(uint32_t base_instruction_id, uint32_t offset,
                            uint32_t fresh_id);
 
-  // - |message_.result_id| must be the result id of an instruction 'base' in
-  //   some block 'blk'.
+  // - |message_.base_instruction_id| must be the result id of an instruction
+  //   'base' in some block 'blk'.
   // - 'blk' must contain an instruction 'inst' located |message_.offset|
-  //   instructions after 'inst' (if |message_.offset| = 0 then 'inst' =
+  //   instructions after 'base' (if |message_.offset| = 0 then 'inst' =
   //   'base').
   // - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards
   //   appear in a new block that 'blk' directly jumps to must be valid.
@@ -52,14 +52,6 @@
   protobufs::Transformation ToMessage() const override;
 
  private:
-  // Returns:
-  // - (true, block->end()) if the relevant instruction is in this block
-  //      but inapplicable
-  // - (true, it) if 'it' is an iterator for the relevant instruction
-  // - (false, _) otherwise.
-  std::pair<bool, opt::BasicBlock::iterator> FindInstToSplitBefore(
-      opt::BasicBlock* block) const;
-
   protobufs::TransformationSplitBlock message_;
 };
 
diff --git a/source/fuzz/uniform_buffer_element_descriptor.cpp b/source/fuzz/uniform_buffer_element_descriptor.cpp
index 8c758e4..90fd85e 100644
--- a/source/fuzz/uniform_buffer_element_descriptor.cpp
+++ b/source/fuzz/uniform_buffer_element_descriptor.cpp
@@ -14,7 +14,7 @@
 
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 
-#include <source/opt/instruction.h>
+#include <algorithm>
 
 namespace spvtools {
 namespace fuzz {
diff --git a/source/fuzz/uniform_buffer_element_descriptor.h b/source/fuzz/uniform_buffer_element_descriptor.h
index 23a16f0..d35de57 100644
--- a/source/fuzz/uniform_buffer_element_descriptor.h
+++ b/source/fuzz/uniform_buffer_element_descriptor.h
@@ -15,7 +15,6 @@
 #ifndef SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
 #define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_
 
-#include <algorithm>
 #include <vector>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
@@ -25,8 +24,8 @@
 namespace spvtools {
 namespace fuzz {
 
-// Factory method to create a uniform buffer element descriptor message from an
-// id and list of indices.
+// Factory method to create a uniform buffer element descriptor message from
+// descriptor set and binding ids and a list of indices.
 protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
     uint32_t descriptor_set, uint32_t binding, std::vector<uint32_t>&& indices);
 
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index c0e2925..6a101dd 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -30,6 +30,7 @@
           transformation_add_type_float_test.cpp
           transformation_add_type_int_test.cpp
           transformation_add_type_pointer_test.cpp
+          transformation_copy_object_test.cpp
           transformation_move_block_down_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
diff --git a/test/fuzz/transformation_copy_object_test.cpp b/test/fuzz/transformation_copy_object_test.cpp
new file mode 100644
index 0000000..0c214c8
--- /dev/null
+++ b/test/fuzz/transformation_copy_object_test.cpp
@@ -0,0 +1,539 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_copy_object.h"
+#include "source/fuzz/data_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %8 = OpConstantFalse %6
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  ASSERT_EQ(0, fact_manager.GetIdsForWhichSynonymsAreKnown().size());
+
+  TransformationCopyObject copy_true(7, 5, 1, 100);
+  ASSERT_TRUE(copy_true.IsApplicable(context.get(), fact_manager));
+  copy_true.Apply(context.get(), &fact_manager);
+
+  const std::set<uint32_t>& ids_for_which_synonyms_are_known =
+      fact_manager.GetIdsForWhichSynonymsAreKnown();
+  ASSERT_EQ(1, ids_for_which_synonyms_are_known.size());
+  ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) !=
+              ids_for_which_synonyms_are_known.end());
+  ASSERT_EQ(1, fact_manager.GetSynonymsForId(7).size());
+  protobufs::DataDescriptor descriptor_100 = MakeDataDescriptor(100, {});
+  ASSERT_TRUE(DataDescriptorEquals()(&descriptor_100,
+                                     &fact_manager.GetSynonymsForId(7)[0]));
+
+  TransformationCopyObject copy_false(8, 100, 1, 101);
+  ASSERT_TRUE(copy_false.IsApplicable(context.get(), fact_manager));
+  copy_false.Apply(context.get(), &fact_manager);
+  ASSERT_EQ(2, ids_for_which_synonyms_are_known.size());
+  ASSERT_TRUE(ids_for_which_synonyms_are_known.find(8) !=
+              ids_for_which_synonyms_are_known.end());
+  ASSERT_EQ(1, fact_manager.GetSynonymsForId(8).size());
+  protobufs::DataDescriptor descriptor_101 = MakeDataDescriptor(101, {});
+  ASSERT_TRUE(DataDescriptorEquals()(&descriptor_101,
+                                     &fact_manager.GetSynonymsForId(8)[0]));
+
+  TransformationCopyObject copy_false_again(101, 5, 3, 102);
+  ASSERT_TRUE(copy_false_again.IsApplicable(context.get(), fact_manager));
+  copy_false_again.Apply(context.get(), &fact_manager);
+  ASSERT_EQ(3, ids_for_which_synonyms_are_known.size());
+  ASSERT_TRUE(ids_for_which_synonyms_are_known.find(101) !=
+              ids_for_which_synonyms_are_known.end());
+  ASSERT_EQ(1, fact_manager.GetSynonymsForId(101).size());
+  protobufs::DataDescriptor descriptor_102 = MakeDataDescriptor(102, {});
+  ASSERT_TRUE(DataDescriptorEquals()(&descriptor_102,
+                                     &fact_manager.GetSynonymsForId(101)[0]));
+
+  TransformationCopyObject copy_true_again(7, 102, 1, 103);
+  ASSERT_TRUE(copy_true_again.IsApplicable(context.get(), fact_manager));
+  copy_true_again.Apply(context.get(), &fact_manager);
+  // This does re-uses an id for which synonyms are already known, so the count
+  // of such ids does not change.
+  ASSERT_EQ(3, ids_for_which_synonyms_are_known.size());
+  ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) !=
+              ids_for_which_synonyms_are_known.end());
+  ASSERT_EQ(2, fact_manager.GetSynonymsForId(7).size());
+  protobufs::DataDescriptor descriptor_103 = MakeDataDescriptor(103, {});
+  ASSERT_TRUE(DataDescriptorEquals()(&descriptor_103,
+                                     &fact_manager.GetSynonymsForId(7)[0]) ||
+              DataDescriptorEquals()(&descriptor_103,
+                                     &fact_manager.GetSynonymsForId(7)[1]));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %8 = OpConstantFalse %6
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %100 = OpCopyObject %6 %7
+        %101 = OpCopyObject %6 %8
+        %102 = OpCopyObject %6 %101
+        %103 = OpCopyObject %6 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationCopyObjectTest, CheckIllegalCases) {
+  // The following SPIR-V comes from this GLSL, pushed through spirv-opt
+  // and then doctored a bit.
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // struct S {
+  //   int a;
+  //   float b;
+  // };
+  //
+  // layout(set = 0, binding = 2) uniform block {
+  //   S s;
+  //   lowp float f;
+  //   int ii;
+  // } ubuf;
+  //
+  // layout(location = 0) out vec4 color;
+  //
+  // void main() {
+  //   float c = 0.0;
+  //   lowp float d = 0.0;
+  //   S localS = ubuf.s;
+  //   for (int i = 0; i < ubuf.s.a; i++) {
+  //     switch (ubuf.ii) {
+  //       case 0:
+  //         c += 0.1;
+  //         d += 0.2;
+  //       case 1:
+  //         c += 0.1;
+  //         if (c > d) {
+  //           d += 0.2;
+  //         } else {
+  //           d += c;
+  //         }
+  //         break;
+  //       default:
+  //         i += 1;
+  //         localS.b += d;
+  //     }
+  //   }
+  //   color = vec4(c, d, localS.b, 1.0);
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %80
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "S"
+               OpMemberName %12 0 "a"
+               OpMemberName %12 1 "b"
+               OpName %15 "S"
+               OpMemberName %15 0 "a"
+               OpMemberName %15 1 "b"
+               OpName %16 "block"
+               OpMemberName %16 0 "s"
+               OpMemberName %16 1 "f"
+               OpMemberName %16 2 "ii"
+               OpName %18 "ubuf"
+               OpName %80 "color"
+               OpMemberDecorate %12 0 RelaxedPrecision
+               OpMemberDecorate %15 0 RelaxedPrecision
+               OpMemberDecorate %15 0 Offset 0
+               OpMemberDecorate %15 1 Offset 4
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 RelaxedPrecision
+               OpMemberDecorate %16 1 Offset 16
+               OpMemberDecorate %16 2 RelaxedPrecision
+               OpMemberDecorate %16 2 Offset 20
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 2
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %43 RelaxedPrecision
+               OpDecorate %53 RelaxedPrecision
+               OpDecorate %62 RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+               OpDecorate %77 RelaxedPrecision
+               OpDecorate %80 Location 0
+               OpDecorate %101 RelaxedPrecision
+               OpDecorate %102 RelaxedPrecision
+               OpDecorate %96 RelaxedPrecision
+               OpDecorate %108 RelaxedPrecision
+               OpDecorate %107 RelaxedPrecision
+               OpDecorate %98 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %9 = OpConstant %6 0
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeStruct %11 %6
+         %15 = OpTypeStruct %11 %6
+         %16 = OpTypeStruct %15 %6 %11
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpConstant %11 0
+         %20 = OpTypePointer Uniform %15
+         %27 = OpConstant %11 1
+         %36 = OpTypePointer Uniform %11
+         %39 = OpTypeBool
+         %41 = OpConstant %11 2
+         %48 = OpConstant %6 0.100000001
+         %51 = OpConstant %6 0.200000003
+         %78 = OpTypeVector %6 4
+         %79 = OpTypePointer Output %78
+         %80 = OpVariable %79 Output
+         %85 = OpConstant %6 1
+         %95 = OpUndef %12
+        %112 = OpTypePointer Uniform %6
+        %113 = OpTypeInt 32 0
+        %114 = OpConstant %113 1
+        %179 = OpTypePointer Function %39
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %180 = OpVariable %179 Function
+        %181 = OpVariable %179 Function
+        %182 = OpVariable %179 Function
+         %21 = OpAccessChain %20 %18 %19
+        %115 = OpAccessChain %112 %21 %114
+        %116 = OpLoad %6 %115
+         %90 = OpCompositeInsert %12 %116 %95 1
+               OpBranch %30
+         %30 = OpLabel
+         %99 = OpPhi %12 %90 %5 %109 %47
+         %98 = OpPhi %6 %9 %5 %107 %47
+         %97 = OpPhi %6 %9 %5 %105 %47
+         %96 = OpPhi %11 %19 %5 %77 %47
+         %37 = OpAccessChain %36 %18 %19 %19
+         %38 = OpLoad %11 %37
+         %40 = OpSLessThan %39 %96 %38
+               OpLoopMerge %32 %47 None
+               OpBranchConditional %40 %31 %32
+         %31 = OpLabel
+         %42 = OpAccessChain %36 %18 %41
+         %43 = OpLoad %11 %42
+               OpSelectionMerge %47 None
+               OpSwitch %43 %46 0 %44 1 %45
+         %46 = OpLabel
+         %69 = OpIAdd %11 %96 %27
+         %72 = OpCompositeExtract %6 %99 1
+         %73 = OpFAdd %6 %72 %98
+         %93 = OpCompositeInsert %12 %73 %99 1
+               OpBranch %47
+         %44 = OpLabel
+         %50 = OpFAdd %6 %97 %48
+         %53 = OpFAdd %6 %98 %51
+               OpBranch %45
+         %45 = OpLabel
+        %101 = OpPhi %6 %98 %31 %53 %44
+        %100 = OpPhi %6 %97 %31 %50 %44
+         %55 = OpFAdd %6 %100 %48
+         %58 = OpFOrdGreaterThan %39 %55 %101
+               OpSelectionMerge %60 None
+               OpBranchConditional %58 %59 %63
+         %59 = OpLabel
+         %62 = OpFAdd %6 %101 %51
+               OpBranch %60
+         %63 = OpLabel
+         %66 = OpFAdd %6 %101 %55
+               OpBranch %60
+         %60 = OpLabel
+        %108 = OpPhi %6 %62 %59 %66 %63
+               OpBranch %47
+         %47 = OpLabel
+        %109 = OpPhi %12 %93 %46 %99 %60
+        %107 = OpPhi %6 %98 %46 %108 %60
+        %105 = OpPhi %6 %97 %46 %55 %60
+        %102 = OpPhi %11 %69 %46 %96 %60
+         %77 = OpIAdd %11 %102 %27
+               OpBranch %30
+         %32 = OpLabel
+         %84 = OpCompositeExtract %6 %99 1
+         %86 = OpCompositeConstruct %78 %97 %98 %84 %85
+               OpStore %80 %86
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Inapplicable because %18 is decorated.
+  ASSERT_FALSE(TransformationCopyObject(18, 21, 0, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because %77 is decorated.
+  ASSERT_FALSE(TransformationCopyObject(17, 17, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because %80 is decorated.
+  ASSERT_FALSE(TransformationCopyObject(80, 77, 0, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because %84 is not available at the requested point
+  ASSERT_FALSE(TransformationCopyObject(84, 32, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Fine because %84 is available at the requested point
+  ASSERT_TRUE(TransformationCopyObject(84, 32, 2, 200)
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because id %9 is already in use
+  ASSERT_FALSE(TransformationCopyObject(84, 32, 2, 9)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because the requested point is not in a block
+  ASSERT_FALSE(TransformationCopyObject(84, 86, 3, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because %9 is not in a function
+  ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because %9 is not in a function
+  ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because the insert point is right before, or inside, a chunk
+  // of OpPhis
+  ASSERT_FALSE(TransformationCopyObject(9, 30, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationCopyObject(9, 99, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // OK, because the insert point is just after a chunk of OpPhis.
+  ASSERT_TRUE(TransformationCopyObject(9, 96, 1, 200)
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because the insert point is right after an OpSelectionMerge
+  ASSERT_FALSE(TransformationCopyObject(9, 58, 2, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // OK, because the insert point is right before the OpSelectionMerge
+  ASSERT_TRUE(TransformationCopyObject(9, 58, 1, 200)
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because the insert point is right after an OpSelectionMerge
+  ASSERT_FALSE(TransformationCopyObject(9, 43, 2, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // OK, because the insert point is right before the OpSelectionMerge
+  ASSERT_TRUE(TransformationCopyObject(9, 43, 1, 200)
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because the insert point is right after an OpLoopMerge
+  ASSERT_FALSE(TransformationCopyObject(9, 40, 2, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // OK, because the insert point is right before the OpLoopMerge
+  ASSERT_TRUE(TransformationCopyObject(9, 40, 1, 200)
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because id %300 does not exist
+  ASSERT_FALSE(TransformationCopyObject(300, 40, 1, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Inapplicable because the following instruction is OpVariable
+  ASSERT_FALSE(TransformationCopyObject(9, 180, 0, 200)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationCopyObject(9, 181, 0, 200)
+                   .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(TransformationCopyObject(9, 182, 0, 200)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // OK, because this is just past the group of OpVariable instructions.
+  ASSERT_TRUE(TransformationCopyObject(9, 182, 1, 200)
+                  .IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationCopyObjectTest, MiscellaneousCopies) {
+  // The following SPIR-V comes from this GLSL:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // float g;
+  //
+  // vec4 h;
+  //
+  // void main() {
+  //   int a;
+  //   int b;
+  //   b = int(g);
+  //   h.x = float(a);
+  // }
+
+  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 "b"
+               OpName %11 "g"
+               OpName %16 "h"
+               OpName %17 "a"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeFloat 32
+         %10 = OpTypePointer Private %9
+         %11 = OpVariable %10 Private
+         %14 = OpTypeVector %9 4
+         %15 = OpTypePointer Private %14
+         %16 = OpVariable %15 Private
+         %20 = OpTypeInt 32 0
+         %21 = OpConstant %20 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %17 = OpVariable %7 Function
+         %12 = OpLoad %9 %11
+         %13 = OpConvertFToS %6 %12
+               OpStore %8 %13
+         %18 = OpLoad %6 %17
+         %19 = OpConvertSToF %9 %18
+         %22 = OpAccessChain %10 %16 %21
+               OpStore %22 %19
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  std::vector<TransformationCopyObject> transformations = {
+      TransformationCopyObject(19, 22, 1, 100),
+      TransformationCopyObject(22, 22, 1, 101),
+      TransformationCopyObject(12, 22, 1, 102),
+      TransformationCopyObject(11, 22, 1, 103),
+      TransformationCopyObject(16, 22, 1, 104),
+      TransformationCopyObject(8, 22, 1, 105),
+      TransformationCopyObject(17, 22, 1, 106)};
+
+  for (auto& transformation : transformations) {
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b"
+               OpName %11 "g"
+               OpName %16 "h"
+               OpName %17 "a"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeFloat 32
+         %10 = OpTypePointer Private %9
+         %11 = OpVariable %10 Private
+         %14 = OpTypeVector %9 4
+         %15 = OpTypePointer Private %14
+         %16 = OpVariable %15 Private
+         %20 = OpTypeInt 32 0
+         %21 = OpConstant %20 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %17 = OpVariable %7 Function
+         %12 = OpLoad %9 %11
+         %13 = OpConvertFToS %6 %12
+               OpStore %8 %13
+         %18 = OpLoad %6 %17
+         %19 = OpConvertSToF %9 %18
+         %22 = OpAccessChain %10 %16 %21
+        %106 = OpCopyObject %7 %17
+        %105 = OpCopyObject %7 %8
+        %104 = OpCopyObject %15 %16
+        %103 = OpCopyObject %10 %11
+        %102 = OpCopyObject %9 %12
+        %101 = OpCopyObject %10 %22
+        %100 = OpCopyObject %9 %19
+               OpStore %22 %19
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools