spirv-fuzz: Add FuzzerPassAddCopyMemoryInstructions (#3391)

Fixes #3382.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index d147408..c6beeb1 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -39,6 +39,7 @@
         fuzzer_pass.h
         fuzzer_pass_add_access_chains.h
         fuzzer_pass_add_composite_types.h
+        fuzzer_pass_add_copy_memory.h
         fuzzer_pass_add_dead_blocks.h
         fuzzer_pass_add_dead_breaks.h
         fuzzer_pass_add_dead_continues.h
@@ -87,6 +88,7 @@
         transformation_add_constant_composite.h
         transformation_add_constant_null.h
         transformation_add_constant_scalar.h
+        transformation_add_copy_memory.h
         transformation_add_dead_block.h
         transformation_add_dead_break.h
         transformation_add_dead_continue.h
@@ -147,6 +149,7 @@
         fuzzer_pass.cpp
         fuzzer_pass_add_access_chains.cpp
         fuzzer_pass_add_composite_types.cpp
+        fuzzer_pass_add_copy_memory.cpp
         fuzzer_pass_add_dead_blocks.cpp
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_dead_continues.cpp
@@ -194,6 +197,7 @@
         transformation_add_constant_composite.cpp
         transformation_add_constant_null.cpp
         transformation_add_constant_scalar.cpp
+        transformation_add_copy_memory.cpp
         transformation_add_dead_block.cpp
         transformation_add_dead_break.cpp
         transformation_add_dead_continue.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 36e7f90..b7b035e 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -23,6 +23,7 @@
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass_add_access_chains.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"
 #include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
 #include "source/fuzz/fuzzer_pass_add_dead_continues.h"
@@ -201,6 +202,9 @@
     MaybeAddPass<FuzzerPassAddCompositeTypes>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddCopyMemory>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassAddDeadBlocks>(
         &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 97e145b..ec34528 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> kChanceOfAddingCopyMemory = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
@@ -119,6 +120,8 @@
       ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
   chance_of_adding_array_or_struct_type_ =
       ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
+  chance_of_adding_copy_memory_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingCopyMemory);
   chance_of_adding_dead_block_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBlock);
   chance_of_adding_dead_break_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 4598124..a81e287 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -114,6 +114,9 @@
   uint32_t GetChanceOfAddingArrayOrStructType() {
     return chance_of_adding_array_or_struct_type_;
   }
+  uint32_t GetChanceOfAddingCopyMemory() {
+    return chance_of_adding_copy_memory_;
+  }
   uint32_t GetChanceOfAddingDeadBlock() { return chance_of_adding_dead_block_; }
   uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; }
   uint32_t GetChanceOfAddingDeadContinue() {
@@ -274,6 +277,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_copy_memory_;
   uint32_t chance_of_adding_dead_block_;
   uint32_t chance_of_adding_dead_break_;
   uint32_t chance_of_adding_dead_continue_;
diff --git a/source/fuzz/fuzzer_pass_add_copy_memory.cpp b/source/fuzz/fuzzer_pass_add_copy_memory.cpp
new file mode 100644
index 0000000..ed375a1
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_copy_memory.cpp
@@ -0,0 +1,82 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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_copy_memory.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_add_copy_memory.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddCopyMemory::FuzzerPassAddCopyMemory(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddCopyMemory::~FuzzerPassAddCopyMemory() = default;
+
+void FuzzerPassAddCopyMemory::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor) {
+        // Check that we can insert an OpCopyMemory before this instruction.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyMemory,
+                                                          inst_it)) {
+          return;
+        }
+
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingCopyMemory())) {
+          return;
+        }
+
+        // Get all instructions available before |inst_it| according to the
+        // domination rules.
+        auto instructions = FindAvailableInstructions(
+            function, block, inst_it,
+            TransformationAddCopyMemory::IsInstructionSupported);
+
+        if (instructions.empty()) {
+          return;
+        }
+
+        const auto* inst =
+            instructions[GetFuzzerContext()->RandomIndex(instructions)];
+
+        // Decide whether to create global or local variable.
+        auto storage_class = GetFuzzerContext()->ChooseEven()
+                                 ? SpvStorageClassPrivate
+                                 : SpvStorageClassFunction;
+
+        auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
+            GetIRContext(), inst->type_id());
+
+        // Create a pointer type with |storage_class| if needed.
+        FindOrCreatePointerType(pointee_type_id, storage_class);
+
+        ApplyTransformation(TransformationAddCopyMemory(
+            instruction_descriptor, GetFuzzerContext()->GetFreshId(),
+            inst->result_id(), storage_class,
+            FindOrCreateZeroConstant(pointee_type_id)));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_copy_memory.h b/source/fuzz/fuzzer_pass_add_copy_memory.h
new file mode 100644
index 0000000..321e4a1
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_copy_memory.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_COPY_MEMORY_INSTRUCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_COPY_MEMORY_INSTRUCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly decides whether to add OpCopyMemory before some instruction in the
+// module.
+class FuzzerPassAddCopyMemory : public FuzzerPass {
+ public:
+  FuzzerPassAddCopyMemory(opt::IRContext* ir_context,
+                          TransformationContext* transformation_context,
+                          FuzzerContext* fuzzer_context,
+                          protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddCopyMemory() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_COPY_MEMORY_INSTRUCTIONS_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 7e0db3e..068c6d8 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -381,6 +381,7 @@
     TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 50;
     TransformationPermutePhiOperands permute_phi_operands = 51;
     TransformationAddParameter add_parameter = 52;
+    TransformationAddCopyMemory add_copy_memory = 53;
     // Add additional option using the next available number.
   }
 }
@@ -458,6 +459,30 @@
 
 }
 
+message TransformationAddCopyMemory {
+
+  // Adds an OpCopyMemory instruction into the module.
+  // Creates either a global or a local variable (based on
+  // |storage_class| field) to copy the target into.
+
+  // OpCopyMemory will be inserted before this instruction.
+  InstructionDescriptor instruction_descriptor = 1;
+
+  // Fresh id to copy memory into.
+  uint32 fresh_id = 2;
+
+  // Source to copy memory from.
+  uint32 source_id = 3;
+
+  // Storage class for the target variable. Can be either Function or Private.
+  uint32 storage_class = 4;
+
+  // Result id for the variable's initializer operand. Its type must be equal to
+  // variable's pointee type.
+  uint32 initializer_id = 5;
+
+}
+
 message TransformationAddDeadBlock {
 
   // Adds a new block to the module that is statically reachable from an
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index ac16a9f..252c750 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -22,6 +22,7 @@
 #include "source/fuzz/transformation_add_constant_composite.h"
 #include "source/fuzz/transformation_add_constant_null.h"
 #include "source/fuzz/transformation_add_constant_scalar.h"
+#include "source/fuzz/transformation_add_copy_memory.h"
 #include "source/fuzz/transformation_add_dead_block.h"
 #include "source/fuzz/transformation_add_dead_break.h"
 #include "source/fuzz/transformation_add_dead_continue.h"
@@ -93,6 +94,8 @@
     case protobufs::Transformation::TransformationCase::kAddConstantScalar:
       return MakeUnique<TransformationAddConstantScalar>(
           message.add_constant_scalar());
+    case protobufs::Transformation::TransformationCase::kAddCopyMemory:
+      return MakeUnique<TransformationAddCopyMemory>(message.add_copy_memory());
     case protobufs::Transformation::TransformationCase::kAddDeadBlock:
       return MakeUnique<TransformationAddDeadBlock>(message.add_dead_block());
     case protobufs::Transformation::TransformationCase::kAddDeadBreak:
diff --git a/source/fuzz/transformation_add_copy_memory.cpp b/source/fuzz/transformation_add_copy_memory.cpp
new file mode 100644
index 0000000..e9c401d
--- /dev/null
+++ b/source/fuzz/transformation_add_copy_memory.cpp
@@ -0,0 +1,193 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_copy_memory.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddCopyMemory::TransformationAddCopyMemory(
+    const protobufs::TransformationAddCopyMemory& message)
+    : message_(message) {}
+
+TransformationAddCopyMemory::TransformationAddCopyMemory(
+    const protobufs::InstructionDescriptor& instruction_descriptor,
+    uint32_t fresh_id, uint32_t source_id, SpvStorageClass storage_class,
+    uint32_t initializer_id) {
+  *message_.mutable_instruction_descriptor() = instruction_descriptor;
+  message_.set_fresh_id(fresh_id);
+  message_.set_source_id(source_id);
+  message_.set_storage_class(storage_class);
+  message_.set_initializer_id(initializer_id);
+}
+
+bool TransformationAddCopyMemory::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // Check that target id is fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // Check that instruction descriptor is valid. This also checks that
+  // |message_.instruction_descriptor| is not a global instruction.
+  auto* inst = FindInstruction(message_.instruction_descriptor(), ir_context);
+  if (!inst) {
+    return false;
+  }
+
+  // Check that we can insert OpCopyMemory before |instruction_descriptor|.
+  auto iter = fuzzerutil::GetIteratorForInstruction(
+      ir_context->get_instr_block(inst), inst);
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyMemory, iter)) {
+    return false;
+  }
+
+  // Check that source instruction exists and is valid.
+  auto* source_inst =
+      ir_context->get_def_use_mgr()->GetDef(message_.source_id());
+  if (!source_inst || !IsInstructionSupported(ir_context, source_inst)) {
+    return false;
+  }
+
+  // |storage_class| is either Function or Private.
+  if (message_.storage_class() != SpvStorageClassFunction &&
+      message_.storage_class() != SpvStorageClassPrivate) {
+    return false;
+  }
+
+  auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      ir_context, source_inst->type_id());
+
+  // OpTypePointer with |message_.storage_class| exists.
+  if (!fuzzerutil::MaybeGetPointerType(
+          ir_context, pointee_type_id,
+          static_cast<SpvStorageClass>(message_.storage_class()))) {
+    return false;
+  }
+
+  // Check that |initializer_id| exists and has valid type.
+  const auto* initializer_inst =
+      ir_context->get_def_use_mgr()->GetDef(message_.initializer_id());
+  if (!initializer_inst || initializer_inst->type_id() != pointee_type_id) {
+    return false;
+  }
+
+  // Check that domination rules are satisfied.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, inst,
+                                                    message_.source_id());
+}
+
+void TransformationAddCopyMemory::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Insert OpCopyMemory before |instruction_descriptor|.
+  auto* insert_before_inst =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  assert(insert_before_inst);
+
+  auto insert_before_iter = fuzzerutil::GetIteratorForInstruction(
+      ir_context->get_instr_block(insert_before_inst), insert_before_inst);
+
+  insert_before_iter.InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpCopyMemory, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.source_id()}}}));
+
+  // Add global or local variable to copy memory into.
+  auto storage_class = static_cast<SpvStorageClass>(message_.storage_class());
+  auto type_id = fuzzerutil::MaybeGetPointerType(
+      ir_context,
+      fuzzerutil::GetPointeeTypeIdFromPointerType(
+          ir_context, fuzzerutil::GetTypeId(ir_context, message_.source_id())),
+      storage_class);
+
+  if (storage_class == SpvStorageClassPrivate) {
+    fuzzerutil::AddGlobalVariable(ir_context, message_.fresh_id(), type_id,
+                                  storage_class, message_.initializer_id());
+  } else {
+    assert(storage_class == SpvStorageClassFunction &&
+           "Storage class can be either Private or Function");
+    fuzzerutil::AddLocalVariable(ir_context, message_.fresh_id(), type_id,
+                                 ir_context->get_instr_block(insert_before_inst)
+                                     ->GetParent()
+                                     ->result_id(),
+                                 message_.initializer_id());
+  }
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Even though the copy memory instruction will - at least temporarily - lead
+  // to the destination and source pointers referring to identical values, this
+  // fact is not guaranteed to hold throughout execution of the SPIR-V code
+  // since the source pointer could be over-written. We thus assume nothing
+  // about the destination pointer, and record this fact so that the destination
+  // pointer can be used freely by other fuzzer passes.
+  transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      message_.fresh_id());
+
+  // Make sure our changes are analyzed
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddCopyMemory::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_copy_memory() = message_;
+  return result;
+}
+
+bool TransformationAddCopyMemory::IsInstructionSupported(
+    opt::IRContext* ir_context, opt::Instruction* inst) {
+  if (!inst->result_id() || !inst->type_id() ||
+      inst->opcode() == SpvOpConstantNull || inst->opcode() == SpvOpUndef) {
+    return false;
+  }
+
+  const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
+  assert(type && "Instruction must have a valid type");
+
+  return type->AsPointer() &&
+         CanUsePointeeWithCopyMemory(*type->AsPointer()->pointee_type());
+}
+
+bool TransformationAddCopyMemory::CanUsePointeeWithCopyMemory(
+    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:
+      return true;
+    case opt::analysis::Type::kVector:
+      return CanUsePointeeWithCopyMemory(*type.AsVector()->element_type());
+    case opt::analysis::Type::kMatrix:
+      return CanUsePointeeWithCopyMemory(*type.AsMatrix()->element_type());
+    case opt::analysis::Type::kStruct:
+      return std::all_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [](const opt::analysis::Type* element) {
+                           return CanUsePointeeWithCopyMemory(*element);
+                         });
+    default:
+      return false;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_copy_memory.h b/source/fuzz/transformation_add_copy_memory.h
new file mode 100644
index 0000000..138d992
--- /dev/null
+++ b/source/fuzz/transformation_add_copy_memory.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_COPY_MEMORY_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_COPY_MEMORY_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 TransformationAddCopyMemory : public Transformation {
+ public:
+  explicit TransformationAddCopyMemory(
+      const protobufs::TransformationAddCopyMemory& message);
+
+  TransformationAddCopyMemory(
+      const protobufs::InstructionDescriptor& instruction_descriptor,
+      uint32_t fresh_id, uint32_t source_id, SpvStorageClass storage_class,
+      uint32_t initializer_id);
+
+  // - |instruction_descriptor| must point to a valid instruction in the module.
+  // - it should be possible to insert OpCopyMemory before
+  //   |instruction_descriptor| (i.e. the module remains valid after the
+  //   insertion).
+  // - |source_id| must be a result id for some valid instruction in the module.
+  // - |fresh_id| must be a fresh id to copy memory into.
+  // - type of |source_id| must be OpTypePointer where pointee can be used with
+  //   OpCopyMemory.
+  // - |storage_class| must be either Private or Function.
+  // - type ids of instructions with result ids |source_id| and |initialize_id|
+  //   must be the same.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // A global or local variable with id |target_id| and |storage_class| class is
+  // created. An 'OpCopyMemory %fresh_id %source_id' instruction is inserted
+  // before the |instruction_descriptor|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if we can copy memory from |instruction| using OpCopyMemory.
+  static bool IsInstructionSupported(opt::IRContext* ir_context,
+                                     opt::Instruction* inst);
+
+ private:
+  // Returns whether the type, pointed to by some OpTypePointer, can be used
+  // with OpCopyMemory instruction.
+  static bool CanUsePointeeWithCopyMemory(const opt::analysis::Type& type);
+
+  protobufs::TransformationAddCopyMemory message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_COPY_MEMORY_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 57d770d..3b710e5 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -29,6 +29,7 @@
           transformation_add_constant_composite_test.cpp
           transformation_add_constant_null_test.cpp
           transformation_add_constant_scalar_test.cpp
+          transformation_add_copy_memory_test.cpp
           transformation_add_dead_block_test.cpp
           transformation_add_dead_break_test.cpp
           transformation_add_dead_continue_test.cpp
diff --git a/test/fuzz/transformation_add_copy_memory_test.cpp b/test/fuzz/transformation_add_copy_memory_test.cpp
new file mode 100644
index 0000000..66a15f4
--- /dev/null
+++ b/test/fuzz/transformation_add_copy_memory_test.cpp
@@ -0,0 +1,384 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_copy_memory.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddCopyMemoryTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %19 RelaxedPrecision
+               OpMemberDecorate %66 0 RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+         %78 = OpTypePointer Private %6
+          %8 = OpTypeFunction %6 %7
+         %17 = OpTypeInt 32 1
+         %18 = OpTypePointer Function %17
+         %79 = OpTypePointer Private %17
+         %20 = OpConstant %17 0
+         %21 = OpTypeFloat 32
+         %22 = OpTypePointer Function %21
+         %80 = OpTypePointer Private %21
+         %24 = OpConstant %21 0
+         %25 = OpConstantFalse %6
+         %32 = OpConstantTrue %6
+         %33 = OpTypeVector %21 4
+         %34 = OpTypePointer Function %33
+         %81 = OpTypePointer Private %33
+         %36 = OpConstantComposite %33 %24 %24 %24 %24
+         %37 = OpTypeMatrix %33 4
+         %84 = OpConstantComposite %37 %36 %36 %36 %36
+         %38 = OpTypePointer Function %37
+         %82 = OpTypePointer Private %37
+         %44 = OpConstant %21 1
+         %66 = OpTypeStruct %17 %21 %6 %33 %37
+         %85 = OpConstantComposite %66 %20 %24 %25 %36 %84
+         %67 = OpTypePointer Function %66
+         %83 = OpTypePointer Private %66
+         %86 = OpVariable %79 Private %20
+         %87 = OpUndef %79
+         %88 = OpConstantNull %79
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpVariable %18 Function
+         %23 = OpVariable %22 Function
+         %26 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+         %35 = OpVariable %34 Function
+         %39 = OpVariable %38 Function
+         %68 = OpVariable %67 Function
+               OpStore %19 %20
+               OpStore %23 %24
+               OpStore %26 %25
+         %27 = OpFunctionCall %6 %10 %26
+               OpSelectionMerge %29 None
+               OpBranchConditional %27 %28 %31
+         %28 = OpLabel
+               OpBranch %29
+         %31 = OpLabel
+               OpBranch %29
+         %76 = OpLabel
+         %77 = OpLogicalEqual %6 %25 %32
+               OpBranch %29
+         %29 = OpLabel
+         %75 = OpPhi %6 %25 %31 %32 %28 %77 %76
+               OpStore %30 %75
+         %40 = OpLoad %33 %35
+         %41 = OpLoad %33 %35
+         %42 = OpLoad %33 %35
+         %43 = OpLoad %33 %35
+         %45 = OpCompositeExtract %21 %40 0
+         %46 = OpCompositeExtract %21 %40 1
+         %47 = OpCompositeExtract %21 %40 2
+         %48 = OpCompositeExtract %21 %40 3
+         %49 = OpCompositeExtract %21 %41 0
+         %50 = OpCompositeExtract %21 %41 1
+         %51 = OpCompositeExtract %21 %41 2
+         %52 = OpCompositeExtract %21 %41 3
+         %53 = OpCompositeExtract %21 %42 0
+         %54 = OpCompositeExtract %21 %42 1
+         %55 = OpCompositeExtract %21 %42 2
+         %56 = OpCompositeExtract %21 %42 3
+         %57 = OpCompositeExtract %21 %43 0
+         %58 = OpCompositeExtract %21 %43 1
+         %59 = OpCompositeExtract %21 %43 2
+         %60 = OpCompositeExtract %21 %43 3
+         %61 = OpCompositeConstruct %33 %45 %46 %47 %48
+         %62 = OpCompositeConstruct %33 %49 %50 %51 %52
+         %63 = OpCompositeConstruct %33 %53 %54 %55 %56
+         %64 = OpCompositeConstruct %33 %57 %58 %59 %60
+         %65 = OpCompositeConstruct %37 %61 %62 %63 %64
+               OpStore %39 %65
+         %69 = OpLoad %17 %19
+         %70 = OpLoad %21 %23
+         %71 = OpLoad %6 %30
+         %72 = OpLoad %33 %35
+         %73 = OpLoad %37 %39
+         %74 = OpCompositeConstruct %66 %69 %70 %71 %72 %73
+               OpStore %68 %74
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+               OpReturnValue %14
+               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;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Target id is not fresh (59).
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 59, 19,
+                   SpvStorageClassPrivate, 20)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Instruction descriptor is invalid (id 89 is undefined).
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(89, SpvOpVariable, 0), 89, 19,
+                   SpvStorageClassPrivate, 20)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Cannot insert OpCopyMemory before OpPhi.
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(75, SpvOpPhi, 0),
+                                  89, 19, SpvStorageClassPrivate, 20)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Source instruction is invalid.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 76,
+                   SpvStorageClassPrivate, 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Source instruction's type doesn't exist.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 5,
+                   SpvStorageClassPrivate, 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Source instruction's type is invalid.
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
+                                  89, 40, SpvStorageClassPrivate, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Source instruction is OpUndef.
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
+                                  89, 87, SpvStorageClassPrivate, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Source instruction is OpConstantNull.
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
+                                  89, 88, SpvStorageClassPrivate, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Storage class is invalid.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 19,
+                   SpvStorageClassWorkgroup, 20)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Initializer is 0.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 19,
+                   SpvStorageClassPrivate, 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Initializer has wrong type.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 19,
+                   SpvStorageClassPrivate, 25)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Source and target instructions are in different functions.
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(13, SpvOpLoad, 0),
+                                  89, 19, SpvStorageClassPrivate, 20)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Source instruction doesn't dominate the target instruction.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(77, SpvOpLogicalEqual, 0), 89, 19,
+                   SpvStorageClassPrivate, 20)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Source and target instructions are the same.
+  ASSERT_FALSE(TransformationAddCopyMemory(
+                   MakeInstructionDescriptor(19, SpvOpVariable, 0), 89, 19,
+                   SpvStorageClassPrivate, 20)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Correct transformations.
+  uint32_t fresh_id = 89;
+  auto descriptor = MakeInstructionDescriptor(27, SpvOpFunctionCall, 0);
+  std::vector<uint32_t> source_ids = {19, 23, 26, 30, 35, 39, 68, 86};
+  std::vector<uint32_t> initializers = {20, 24, 25, 25, 36, 84, 85, 20};
+  std::vector<SpvStorageClass> storage_classes = {SpvStorageClassPrivate,
+                                                  SpvStorageClassFunction};
+  for (size_t i = 0, n = source_ids.size(); i < n; ++i) {
+    TransformationAddCopyMemory transformation(
+        descriptor, fresh_id, source_ids[i],
+        storage_classes[i % storage_classes.size()], initializers[i]);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(fresh_id));
+    fresh_id++;
+  }
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %19 RelaxedPrecision
+               OpMemberDecorate %66 0 RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+         %78 = OpTypePointer Private %6
+          %8 = OpTypeFunction %6 %7
+         %17 = OpTypeInt 32 1
+         %18 = OpTypePointer Function %17
+         %79 = OpTypePointer Private %17
+         %20 = OpConstant %17 0
+         %21 = OpTypeFloat 32
+         %22 = OpTypePointer Function %21
+         %80 = OpTypePointer Private %21
+         %24 = OpConstant %21 0
+         %25 = OpConstantFalse %6
+         %32 = OpConstantTrue %6
+         %33 = OpTypeVector %21 4
+         %34 = OpTypePointer Function %33
+         %81 = OpTypePointer Private %33
+         %36 = OpConstantComposite %33 %24 %24 %24 %24
+         %37 = OpTypeMatrix %33 4
+         %84 = OpConstantComposite %37 %36 %36 %36 %36
+         %38 = OpTypePointer Function %37
+         %82 = OpTypePointer Private %37
+         %44 = OpConstant %21 1
+         %66 = OpTypeStruct %17 %21 %6 %33 %37
+         %85 = OpConstantComposite %66 %20 %24 %25 %36 %84
+         %67 = OpTypePointer Function %66
+         %83 = OpTypePointer Private %66
+         %86 = OpVariable %79 Private %20
+         %87 = OpUndef %79
+         %88 = OpConstantNull %79
+         %89 = OpVariable %79 Private %20
+         %91 = OpVariable %78 Private %25
+         %93 = OpVariable %81 Private %36
+         %95 = OpVariable %83 Private %85
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %96 = OpVariable %18 Function %20
+         %94 = OpVariable %38 Function %84
+         %92 = OpVariable %7 Function %25
+         %90 = OpVariable %22 Function %24
+         %19 = OpVariable %18 Function
+         %23 = OpVariable %22 Function
+         %26 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+         %35 = OpVariable %34 Function
+         %39 = OpVariable %38 Function
+         %68 = OpVariable %67 Function
+               OpStore %19 %20
+               OpStore %23 %24
+               OpStore %26 %25
+               OpCopyMemory %89 %19
+               OpCopyMemory %90 %23
+               OpCopyMemory %91 %26
+               OpCopyMemory %92 %30
+               OpCopyMemory %93 %35
+               OpCopyMemory %94 %39
+               OpCopyMemory %95 %68
+               OpCopyMemory %96 %86
+         %27 = OpFunctionCall %6 %10 %26
+               OpSelectionMerge %29 None
+               OpBranchConditional %27 %28 %31
+         %28 = OpLabel
+               OpBranch %29
+         %31 = OpLabel
+               OpBranch %29
+         %76 = OpLabel
+         %77 = OpLogicalEqual %6 %25 %32
+               OpBranch %29
+         %29 = OpLabel
+         %75 = OpPhi %6 %25 %31 %32 %28 %77 %76
+               OpStore %30 %75
+         %40 = OpLoad %33 %35
+         %41 = OpLoad %33 %35
+         %42 = OpLoad %33 %35
+         %43 = OpLoad %33 %35
+         %45 = OpCompositeExtract %21 %40 0
+         %46 = OpCompositeExtract %21 %40 1
+         %47 = OpCompositeExtract %21 %40 2
+         %48 = OpCompositeExtract %21 %40 3
+         %49 = OpCompositeExtract %21 %41 0
+         %50 = OpCompositeExtract %21 %41 1
+         %51 = OpCompositeExtract %21 %41 2
+         %52 = OpCompositeExtract %21 %41 3
+         %53 = OpCompositeExtract %21 %42 0
+         %54 = OpCompositeExtract %21 %42 1
+         %55 = OpCompositeExtract %21 %42 2
+         %56 = OpCompositeExtract %21 %42 3
+         %57 = OpCompositeExtract %21 %43 0
+         %58 = OpCompositeExtract %21 %43 1
+         %59 = OpCompositeExtract %21 %43 2
+         %60 = OpCompositeExtract %21 %43 3
+         %61 = OpCompositeConstruct %33 %45 %46 %47 %48
+         %62 = OpCompositeConstruct %33 %49 %50 %51 %52
+         %63 = OpCompositeConstruct %33 %53 %54 %55 %56
+         %64 = OpCompositeConstruct %33 %57 %58 %59 %60
+         %65 = OpCompositeConstruct %37 %61 %62 %63 %64
+               OpStore %39 %65
+         %69 = OpLoad %17 %19
+         %70 = OpLoad %21 %23
+         %71 = OpLoad %6 %30
+         %72 = OpLoad %33 %35
+         %73 = OpLoad %37 %39
+         %74 = OpCompositeConstruct %66 %69 %70 %71 %72 %73
+               OpStore %68 %74
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+               OpReturnValue %14
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
\ No newline at end of file