spirv-fuzz: adds TransformationReplaceCopyMemoryWithLoadStore (#3575)

Adds a transformation that replaces instruction OpCopyMemory with
loading the source variable to an intermediate value and storing this
value into the target variable of the original OpCopyMemory instruction.

Fixes #3352
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 2d76bb2..f1309f3 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -73,6 +73,7 @@
         fuzzer_pass_permute_function_parameters.h
         fuzzer_pass_permute_phi_operands.h
         fuzzer_pass_push_ids_through_variables.h
+        fuzzer_pass_replace_copy_memories_with_loads_stores.h
         fuzzer_pass_replace_copy_objects_with_stores_loads.h
         fuzzer_pass_replace_linear_algebra_instructions.h
         fuzzer_pass_replace_parameter_with_global.h
@@ -137,6 +138,7 @@
         transformation_record_synonymous_constants.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
+        transformation_replace_copy_memory_with_load_store.h
         transformation_replace_copy_object_with_store_load.h
         transformation_replace_id_with_synonym.h
         transformation_replace_linear_algebra_instruction.h
@@ -198,6 +200,7 @@
         fuzzer_pass_permute_function_parameters.cpp
         fuzzer_pass_permute_phi_operands.cpp
         fuzzer_pass_push_ids_through_variables.cpp
+        fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
         fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
         fuzzer_pass_replace_linear_algebra_instructions.cpp
         fuzzer_pass_replace_parameter_with_global.cpp
@@ -261,6 +264,7 @@
         transformation_record_synonymous_constants.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
+        transformation_replace_copy_memory_with_load_store.cpp
         transformation_replace_copy_object_with_store_load.cpp
         transformation_replace_id_with_synonym.cpp
         transformation_replace_linear_algebra_instruction.cpp
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 01f54ed..205e190 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -77,6 +77,8 @@
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
+    {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
     {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
@@ -212,6 +214,8 @@
       ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
   chance_of_pushing_id_through_variable_ =
       ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
+  chance_of_replacing_copy_memory_with_load_store_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore);
   chance_of_replacing_copyobject_with_store_load_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingCopyObjectWithStoreLoad);
   chance_of_replacing_id_with_synonym_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index acc0ac4..8fc6c15 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -212,6 +212,9 @@
   uint32_t GetChanceOfPushingIdThroughVariable() {
     return chance_of_pushing_id_through_variable_;
   }
+  uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() {
+    return chance_of_replacing_copy_memory_with_load_store_;
+  }
   uint32_t GetChanceOfReplacingCopyObjectWithStoreLoad() {
     return chance_of_replacing_copyobject_with_store_load_;
   }
@@ -357,6 +360,7 @@
   uint32_t chance_of_permuting_parameters_;
   uint32_t chance_of_permuting_phi_operands_;
   uint32_t chance_of_pushing_id_through_variable_;
+  uint32_t chance_of_replacing_copy_memory_with_load_store_;
   uint32_t chance_of_replacing_copyobject_with_store_load_;
   uint32_t chance_of_replacing_id_with_synonym_;
   uint32_t chance_of_replacing_linear_algebra_instructions_;
diff --git a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
new file mode 100644
index 0000000..6847146
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
@@ -0,0 +1,58 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassReplaceCopyMemoriesWithLoadsStores::
+    FuzzerPassReplaceCopyMemoriesWithLoadsStores(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceCopyMemoriesWithLoadsStores::
+    ~FuzzerPassReplaceCopyMemoriesWithLoadsStores() = default;
+
+void FuzzerPassReplaceCopyMemoriesWithLoadsStores::Apply() {
+  GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
+    // Randomly decide whether to replace the OpCopyMemory.
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()
+                ->GetChanceOfReplacingCopyMemoryWithLoadStore())) {
+      return;
+    }
+
+    // The instruction must be OpCopyMemory.
+    if (instruction->opcode() != SpvOpCopyMemory) {
+      return;
+    }
+
+    // Apply the transformation replacing OpCopyMemory with OpLoad and OpStore.
+    ApplyTransformation(TransformationReplaceCopyMemoryWithLoadStore(
+        GetFuzzerContext()->GetFreshId(),
+        MakeInstructionDescriptor(GetIRContext(), instruction)));
+  });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h
new file mode 100644
index 0000000..2a89006
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H
+#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Replaces instructions OpCopyMemory with loading the source variable to
+// an intermediate value and storing this value into the target variable of
+// the original OpCopyMemory instruction.
+class FuzzerPassReplaceCopyMemoriesWithLoadsStores : public FuzzerPass {
+ public:
+  FuzzerPassReplaceCopyMemoriesWithLoadsStores(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceCopyMemoriesWithLoadsStores() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 9a01a17..968e084 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -405,6 +405,7 @@
     TransformationAddRelaxedDecoration add_relaxed_decoration = 58;
     TransformationReplaceParamsWithStruct replace_params_with_struct = 59;
     TransformationReplaceCopyObjectWithStoreLoad replace_copy_object_with_store_load = 60;
+    TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61;
     // Add additional option using the next available number.
   }
 }
@@ -1275,6 +1276,20 @@
 
 }
 
+message TransformationReplaceCopyMemoryWithLoadStore {
+
+   // A transformation that replaces instructions OpCopyMemory with loading
+   // the source variable to an intermediate value and storing this value into the
+   // target variable of the original OpCopyMemory instruction.
+
+   // The intermediate value.
+   uint32 fresh_id = 1;
+
+   // The instruction descriptor to OpCopyMemory. It is necessary, because
+   // OpCopyMemory doesn't have a result id.
+   InstructionDescriptor copy_memory_instruction_descriptor = 2;
+}
+
 message TransformationReplaceCopyObjectWithStoreLoad {
 
   // A transformation that replaces instruction OpCopyObject with
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 53b0f0c..a9fa611 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -62,6 +62,7 @@
 #include "source/fuzz/transformation_record_synonymous_constants.h"
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
+#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
 #include "source/fuzz/transformation_replace_copy_object_with_store_load.h"
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
 #include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
@@ -218,6 +219,10 @@
       return MakeUnique<TransformationReplaceConstantWithUniform>(
           message.replace_constant_with_uniform());
     case protobufs::Transformation::TransformationCase::
+        kReplaceCopyMemoryWithLoadStore:
+      return MakeUnique<TransformationReplaceCopyMemoryWithLoadStore>(
+          message.replace_copy_memory_with_load_store());
+    case protobufs::Transformation::TransformationCase::
         kReplaceCopyObjectWithStoreLoad:
       return MakeUnique<TransformationReplaceCopyObjectWithStoreLoad>(
           message.replace_copy_object_with_store_load());
diff --git a/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
new file mode 100644
index 0000000..bf6996a
--- /dev/null
+++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
@@ -0,0 +1,127 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceCopyMemoryWithLoadStore::
+    TransformationReplaceCopyMemoryWithLoadStore(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceCopyMemoryWithLoadStore& message)
+    : message_(message) {}
+
+TransformationReplaceCopyMemoryWithLoadStore::
+    TransformationReplaceCopyMemoryWithLoadStore(
+        uint32_t fresh_id, const protobufs::InstructionDescriptor&
+                               copy_memory_instruction_descriptor) {
+  message_.set_fresh_id(fresh_id);
+  *message_.mutable_copy_memory_instruction_descriptor() =
+      copy_memory_instruction_descriptor;
+}
+
+bool TransformationReplaceCopyMemoryWithLoadStore::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |message_.fresh_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+  // The instruction to be replaced must be defined and have opcode
+  // OpCopyMemory.
+  auto copy_memory_instruction = FindInstruction(
+      message_.copy_memory_instruction_descriptor(), ir_context);
+  if (!copy_memory_instruction ||
+      copy_memory_instruction->opcode() != SpvOpCopyMemory) {
+    return false;
+  }
+  return true;
+}
+
+void TransformationReplaceCopyMemoryWithLoadStore::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto copy_memory_instruction = FindInstruction(
+      message_.copy_memory_instruction_descriptor(), ir_context);
+  // |copy_memory_instruction| must be defined.
+  assert(copy_memory_instruction &&
+         copy_memory_instruction->opcode() == SpvOpCopyMemory &&
+         "The required OpCopyMemory instruction must be defined.");
+
+  // Coherence check: Both operands must be pointers.
+
+  // Get types of ids used as a source and target of |copy_memory_instruction|.
+  auto target = ir_context->get_def_use_mgr()->GetDef(
+      copy_memory_instruction->GetSingleWordInOperand(0));
+  auto source = ir_context->get_def_use_mgr()->GetDef(
+      copy_memory_instruction->GetSingleWordInOperand(1));
+  auto target_type_opcode =
+      ir_context->get_def_use_mgr()->GetDef(target->type_id())->opcode();
+  auto source_type_opcode =
+      ir_context->get_def_use_mgr()->GetDef(source->type_id())->opcode();
+
+  // Keep release-mode compilers happy. (No unused variables.)
+  (void)target;
+  (void)source;
+  (void)target_type_opcode;
+  (void)source_type_opcode;
+
+  assert(target_type_opcode == SpvOpTypePointer &&
+         source_type_opcode == SpvOpTypePointer &&
+         "Operands must be of type OpTypePointer");
+
+  // Coherence check: |source| and |target| must point to the same type.
+  uint32_t target_pointee_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      ir_context, target->type_id());
+  uint32_t source_pointee_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      ir_context, source->type_id());
+
+  // Keep release-mode compilers happy. (No unused variables.)
+  (void)target_pointee_type;
+  (void)source_pointee_type;
+
+  assert(target_pointee_type == source_pointee_type &&
+         "Operands must have the same type to which they point to.");
+
+  // First, insert the OpStore instruction before the OpCopyMemory instruction
+  // and then insert the OpLoad instruction before the OpStore instruction.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  FindInstruction(message_.copy_memory_instruction_descriptor(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpStore, 0, 0,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {target->result_id()}},
+               {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}})))
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLoad, target_pointee_type, message_.fresh_id(),
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {source->result_id()}}})));
+
+  // Remove the OpCopyMemory instruction.
+  ir_context->KillInst(copy_memory_instruction);
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation
+TransformationReplaceCopyMemoryWithLoadStore::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_copy_memory_with_load_store() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_copy_memory_with_load_store.h b/source/fuzz/transformation_replace_copy_memory_with_load_store.h
new file mode 100644
index 0000000..70120f8
--- /dev/null
+++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H
+#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_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 TransformationReplaceCopyMemoryWithLoadStore : public Transformation {
+ public:
+  explicit TransformationReplaceCopyMemoryWithLoadStore(
+      const protobufs::TransformationReplaceCopyMemoryWithLoadStore& message);
+
+  TransformationReplaceCopyMemoryWithLoadStore(
+      uint32_t fresh_id, const protobufs::InstructionDescriptor&
+                             copy_memory_instruction_descriptor);
+
+  // - |message_.fresh_id| must be fresh.
+  // - |message_.copy_memory_instruction_descriptor| must refer to an
+  //   OpCopyMemory instruction.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces instruction OpCopyMemory with loading the source variable to an
+  // intermediate value and storing this value into the target variable of the
+  // original OpCopyMemory instruction.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationReplaceCopyMemoryWithLoadStore message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 34afd7f..2d0404e 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -70,6 +70,7 @@
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_copy_object_with_store_load_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
+          transformation_replace_copy_memory_with_load_store_test.cpp
           transformation_replace_id_with_synonym_test.cpp
           transformation_replace_linear_algebra_instruction_test.cpp
           transformation_replace_params_with_struct_test.cpp
diff --git a/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp b/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp
new file mode 100644
index 0000000..2bbe605
--- /dev/null
+++ b/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp
@@ -0,0 +1,151 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceCopyMemoryWithLoadStoreTest, BasicScenarios) {
+  // This is a simple transformation and this test handles the main cases.
+
+  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 "a"
+               OpName %10 "b"
+               OpName %14 "c"
+               OpName %16 "d"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpTypeFloat 32
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 2
+         %17 = OpConstant %12 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %16 = OpVariable %13 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %14 %15
+               OpStore %16 %17
+               OpCopyMemory %8 %10
+               OpCopyMemory %16 %14
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto instruction_descriptor_invalid_1 =
+      MakeInstructionDescriptor(5, SpvOpStore, 0);
+  auto instruction_descriptor_valid_1 =
+      MakeInstructionDescriptor(5, SpvOpCopyMemory, 0);
+  auto instruction_descriptor_valid_2 =
+      MakeInstructionDescriptor(5, SpvOpCopyMemory, 0);
+
+  // Invalid: |source_id| is not a fresh id.
+  auto transformation_invalid_1 = TransformationReplaceCopyMemoryWithLoadStore(
+      15, instruction_descriptor_valid_1);
+  ASSERT_FALSE(transformation_invalid_1.IsApplicable(context.get(),
+                                                     transformation_context));
+
+  // Invalid: |instruction_descriptor_invalid| refers to an instruction OpStore.
+  auto transformation_invalid_2 = TransformationReplaceCopyMemoryWithLoadStore(
+      20, instruction_descriptor_invalid_1);
+  ASSERT_FALSE(transformation_invalid_2.IsApplicable(context.get(),
+                                                     transformation_context));
+
+  auto transformation_valid_1 = TransformationReplaceCopyMemoryWithLoadStore(
+      20, instruction_descriptor_valid_1);
+  ASSERT_TRUE(transformation_valid_1.IsApplicable(context.get(),
+                                                  transformation_context));
+  transformation_valid_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_valid_2 = TransformationReplaceCopyMemoryWithLoadStore(
+      21, instruction_descriptor_valid_2);
+  ASSERT_TRUE(transformation_valid_2.IsApplicable(context.get(),
+                                                  transformation_context));
+  transformation_valid_2.Apply(context.get(), &transformation_context);
+  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 "a"
+               OpName %10 "b"
+               OpName %14 "c"
+               OpName %16 "d"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpTypeFloat 32
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 2
+         %17 = OpConstant %12 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %16 = OpVariable %13 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %14 %15
+               OpStore %16 %17
+         %20 = OpLoad %6 %10
+               OpStore %8 %20
+         %21 = OpLoad %12 %14
+               OpStore %16 %21
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools