spirv-fuzz: Add push id through variable transformation (#3359)

Makes an id synonym by storing an id to a new variable and then
loading it back from that variable.

Fixes #3192.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 6582927..1fadd8f 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -63,6 +63,7 @@
         fuzzer_pass_outline_functions.h
         fuzzer_pass_permute_blocks.h
         fuzzer_pass_permute_function_parameters.h
+        fuzzer_pass_push_ids_through_variables.h
         fuzzer_pass_split_blocks.h
         fuzzer_pass_swap_commutable_operands.h
         fuzzer_pass_toggle_access_chain_instruction.h
@@ -111,6 +112,7 @@
         transformation_move_block_down.h
         transformation_outline_function.h
         transformation_permute_function_parameters.h
+        transformation_push_id_through_variable.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_id_with_synonym.h
@@ -159,6 +161,7 @@
         fuzzer_pass_outline_functions.cpp
         fuzzer_pass_permute_blocks.cpp
         fuzzer_pass_permute_function_parameters.cpp
+        fuzzer_pass_push_ids_through_variables.cpp
         fuzzer_pass_split_blocks.cpp
         fuzzer_pass_swap_commutable_operands.cpp
         fuzzer_pass_toggle_access_chain_instruction.cpp
@@ -206,6 +209,7 @@
         transformation_move_block_down.cpp
         transformation_outline_function.cpp
         transformation_permute_function_parameters.cpp
+        transformation_push_id_through_variable.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_id_with_synonym.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 3343abc..801fae3 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -46,6 +46,7 @@
 #include "source/fuzz/fuzzer_pass_outline_functions.h"
 #include "source/fuzz/fuzzer_pass_permute_blocks.h"
 #include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
+#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
 #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
 #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h"
@@ -249,6 +250,9 @@
     MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassPushIdsThroughVariables>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassSplitBlocks>(
         &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 1779709..923c255 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -62,6 +62,7 @@
 const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
 const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
@@ -157,6 +158,8 @@
       ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
   chance_of_permuting_parameters_ =
       ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
+  chance_of_pushing_id_through_variable_ =
+      ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
   chance_of_replacing_id_with_synonym_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
   chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index dd19d9a..b1392e7 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -179,6 +179,9 @@
   uint32_t GetChanceOfPermutingParameters() {
     return chance_of_permuting_parameters_;
   }
+  uint32_t GetChanceOfPushingIdThroughVariable() {
+    return chance_of_pushing_id_through_variable_;
+  }
   uint32_t GetChanceOfReplacingIdWithSynonym() {
     return chance_of_replacing_id_with_synonym_;
   }
@@ -263,6 +266,7 @@
   uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_outlining_function_;
   uint32_t chance_of_permuting_parameters_;
+  uint32_t chance_of_pushing_id_through_variable_;
   uint32_t chance_of_replacing_id_with_synonym_;
   uint32_t chance_of_splitting_block_;
   uint32_t chance_of_toggling_access_chain_instruction_;
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
new file mode 100644
index 0000000..fcf220a
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
@@ -0,0 +1,109 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_push_ids_through_variables.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_push_id_through_variable.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPushIdsThroughVariables::FuzzerPassPushIdsThroughVariables(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassPushIdsThroughVariables::~FuzzerPassPushIdsThroughVariables() =
+    default;
+
+void FuzzerPassPushIdsThroughVariables::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator instruction_iterator,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        assert(instruction_iterator->opcode() ==
+                   instruction_descriptor.target_instruction_opcode() &&
+               "The opcode of the instruction we might insert before must be "
+               "the same as the opcode in the descriptor for the instruction");
+
+        // Randomly decide whether to try pushing an id through a variable.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfPushingIdThroughVariable())) {
+          return;
+        }
+
+        // The block containing the instruction we are going to insert before
+        // must be reachable.
+        if (!fuzzerutil::BlockIsReachableInItsFunction(GetIRContext(), block)) {
+          return;
+        }
+
+        // It must be valid to insert OpStore and OpLoad instructions
+        // before the instruction to insert before.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                SpvOpStore, instruction_iterator) ||
+            !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                SpvOpLoad, instruction_iterator)) {
+          return;
+        }
+
+        // Randomly decides whether a global or local variable will be added.
+        auto variable_storage_class = GetFuzzerContext()->ChooseEven()
+                                          ? SpvStorageClassPrivate
+                                          : SpvStorageClassFunction;
+
+        // Gets the available basic and pointer types.
+        auto basic_type_ids_and_pointers =
+            GetAvailableBasicTypesAndPointers(variable_storage_class);
+        auto& basic_types = basic_type_ids_and_pointers.first;
+        uint32_t basic_type_id =
+            basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
+
+        // Looks for ids that we might wish to consider pushing through a
+        // variable.
+        std::vector<opt::Instruction*> value_instructions =
+            FindAvailableInstructions(
+                function, block, instruction_iterator,
+                [basic_type_id](opt::IRContext* /*unused*/,
+                                opt::Instruction* instruction) -> bool {
+                  if (!instruction->result_id() || !instruction->type_id()) {
+                    return false;
+                  }
+                  return instruction->type_id() == basic_type_id;
+                });
+
+        if (value_instructions.empty()) {
+          return;
+        }
+
+        // If the pointer type does not exist, then create it.
+        FindOrCreatePointerType(basic_type_id, variable_storage_class);
+
+        // Applies the push id through variable transformation.
+        ApplyTransformation(TransformationPushIdThroughVariable(
+            value_instructions[GetFuzzerContext()->RandomIndex(
+                                   value_instructions)]
+                ->result_id(),
+            GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+            variable_storage_class, instruction_descriptor));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.h b/source/fuzz/fuzzer_pass_push_ids_through_variables.h
new file mode 100644
index 0000000..ffec89e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_PUSH_IDS_THROUGH_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PUSH_IDS_THROUGH_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Adds instructions to the module that store existing ids into fresh variables
+// and immediately load from said variables into new ids, thus creating synonyms
+// between the existing and fresh ids.
+class FuzzerPassPushIdsThroughVariables : public FuzzerPass {
+ public:
+  FuzzerPassPushIdsThroughVariables(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPushIdsThroughVariables();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PUSH_IDS_THROUGH_VARIABLES_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 775b2ad..645b733 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -375,6 +375,7 @@
     TransformationAddConstantNull add_constant_null = 44;
     TransformationComputeDataSynonymFactClosure compute_data_synonym_fact_closure = 45;
     TransformationAdjustBranchWeights adjust_branch_weights = 46;
+    TransformationPushIdThroughVariable push_id_through_variable = 47;
     // Add additional option using the next available number.
   }
 }
@@ -983,6 +984,30 @@
 
 }
 
+message TransformationPushIdThroughVariable {
+
+  // A transformation that makes |value_synonym_id| and |value_id| to be
+  // synonymous by storing |value_id| into |variable_id| and
+  // loading |variable_id| to |value_synonym_id|.
+
+  // The value to be stored.
+  uint32 value_id = 1;
+
+  // A fresh id for the result of the load instruction.
+  uint32 value_synonym_id = 2;
+
+  // A fresh id for the variable to be stored to.
+  uint32 variable_id = 3;
+
+  // The variable storage class (global or local).
+  uint32 variable_storage_class = 4;
+
+  // A descriptor for an instruction which the new OpStore
+  // and OpLoad instructions might be inserted before.
+  InstructionDescriptor instruction_descriptor = 5;
+
+}
+
 message TransformationReplaceBooleanConstantWithConstantBinary {
 
   // A transformation to capture replacing a use of a boolean constant with
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 8b84169..42168cb 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -51,6 +51,7 @@
 #include "source/fuzz/transformation_move_block_down.h"
 #include "source/fuzz/transformation_outline_function.h"
 #include "source/fuzz/transformation_permute_function_parameters.h"
+#include "source/fuzz/transformation_push_id_through_variable.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_id_with_synonym.h"
@@ -163,6 +164,9 @@
         kPermuteFunctionParameters:
       return MakeUnique<TransformationPermuteFunctionParameters>(
           message.permute_function_parameters());
+    case protobufs::Transformation::TransformationCase::kPushIdThroughVariable:
+      return MakeUnique<TransformationPushIdThroughVariable>(
+          message.push_id_through_variable());
     case protobufs::Transformation::TransformationCase::
         kReplaceBooleanConstantWithConstantBinary:
       return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
diff --git a/source/fuzz/transformation_push_id_through_variable.cpp b/source/fuzz/transformation_push_id_through_variable.cpp
new file mode 100644
index 0000000..c691148
--- /dev/null
+++ b/source/fuzz/transformation_push_id_through_variable.cpp
@@ -0,0 +1,159 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_push_id_through_variable.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPushIdThroughVariable::TransformationPushIdThroughVariable(
+    const spvtools::fuzz::protobufs::TransformationPushIdThroughVariable&
+        message)
+    : message_(message) {}
+
+TransformationPushIdThroughVariable::TransformationPushIdThroughVariable(
+    uint32_t value_id, uint32_t value_synonym_id, uint32_t variable_id,
+    uint32_t variable_storage_class,
+    const protobufs::InstructionDescriptor& instruction_descriptor) {
+  message_.set_value_id(value_id);
+  message_.set_value_synonym_id(value_synonym_id);
+  message_.set_variable_id(variable_id);
+  message_.set_variable_storage_class(variable_storage_class);
+  *message_.mutable_instruction_descriptor() = instruction_descriptor;
+}
+
+bool TransformationPushIdThroughVariable::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |message_.value_synonym_id| and |message_.variable_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.value_synonym_id()) ||
+      !fuzzerutil::IsFreshId(ir_context, message_.variable_id())) {
+    return false;
+  }
+
+  // The instruction to insert before must be defined.
+  auto instruction_to_insert_before =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  if (!instruction_to_insert_before) {
+    return false;
+  }
+
+  // It must be valid to insert the OpStore and OpLoad instruction before it.
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+          SpvOpStore, instruction_to_insert_before) ||
+      !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+          SpvOpLoad, instruction_to_insert_before)) {
+    return false;
+  }
+
+  // The instruction to insert before must belong to a reachable block.
+  auto basic_block = ir_context->get_instr_block(instruction_to_insert_before);
+  if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, basic_block)) {
+    return false;
+  }
+
+  // The value instruction must be defined and have a type.
+  auto value_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.value_id());
+  if (!value_instruction || !value_instruction->type_id()) {
+    return false;
+  }
+
+  // A pointer type instruction pointing to the value type must be defined.
+  auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
+      ir_context, value_instruction->type_id(),
+      static_cast<SpvStorageClass>(message_.variable_storage_class()));
+  if (!pointer_type_id) {
+    return false;
+  }
+
+  // |message_.variable_storage_class| must be private or function.
+  assert((message_.variable_storage_class() == SpvStorageClassPrivate ||
+          message_.variable_storage_class() == SpvStorageClassFunction) &&
+         "The variable storage class must be private or function.");
+
+  // |message_.value_id| must be available at the insertion point.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(
+      ir_context, instruction_to_insert_before, message_.value_id());
+}
+
+void TransformationPushIdThroughVariable::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto value_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.value_id());
+
+  // A pointer type instruction pointing to the value type must be defined.
+  auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
+      ir_context, value_instruction->type_id(),
+      static_cast<SpvStorageClass>(message_.variable_storage_class()));
+  assert(pointer_type_id && "The required pointer type must be available.");
+
+  // Adds whether a global or local variable.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.variable_id());
+  if (message_.variable_storage_class() == SpvStorageClassPrivate) {
+    ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpVariable, pointer_type_id, message_.variable_id(),
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassPrivate}}})));
+  } else {
+    ir_context
+        ->get_instr_block(
+            FindInstruction(message_.instruction_descriptor(), ir_context))
+        ->GetParent()
+        ->begin()
+        ->begin()
+        ->InsertBefore(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpVariable, pointer_type_id, message_.variable_id(),
+            opt::Instruction::OperandList({{SPV_OPERAND_TYPE_STORAGE_CLASS,
+                                            {SpvStorageClassFunction}}})));
+  }
+
+  // Stores value id to variable id.
+  FindInstruction(message_.instruction_descriptor(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpStore, 0, 0,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}},
+               {SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
+
+  // Loads variable id to value synonym id.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.value_synonym_id());
+  FindInstruction(message_.instruction_descriptor(), ir_context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLoad, value_instruction->type_id(),
+          message_.value_synonym_id(),
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}}})));
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Adds the fact that |message_.value_synonym_id|
+  // and |message_.value_id| are synonymous.
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.value_synonym_id(), {}),
+      MakeDataDescriptor(message_.value_id(), {}), ir_context);
+}
+
+protobufs::Transformation TransformationPushIdThroughVariable::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_push_id_through_variable() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_push_id_through_variable.h b/source/fuzz/transformation_push_id_through_variable.h
new file mode 100644
index 0000000..e685ff8
--- /dev/null
+++ b/source/fuzz/transformation_push_id_through_variable.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_PUSH_ID_THROUGH_VARIABLE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PUSH_ID_THROUGH_VARIABLE_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 TransformationPushIdThroughVariable : public Transformation {
+ public:
+  explicit TransformationPushIdThroughVariable(
+      const protobufs::TransformationPushIdThroughVariable& message);
+
+  TransformationPushIdThroughVariable(
+      uint32_t value_id, uint32_t value_synonym_fresh_id,
+      uint32_t variable_fresh_id, uint32_t variable_storage_class,
+      const protobufs::InstructionDescriptor& instruction_descriptor);
+
+  // - |message_.value_id| must be an instruction result id that has the same
+  //   type as the pointee type of |message_.pointer_id|
+  // - |message_.value_synonym_id| must be fresh
+  // - |message_.variable_id| must be fresh
+  // - |message_.variable_storage_class| must be either StorageClassPrivate or
+  //   StorageClassFunction
+  // - |message_.instruction_descriptor| must identify an instruction
+  //   which it is valid to insert the OpStore and OpLoad instructions before it
+  //   and must be belongs to a reachable block.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Stores |value_id| to |variable_id|, loads |variable_id| to
+  // |value_synonym_id| and adds the fact that |value_synonym_id| and |value_id|
+  // are synonymous.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationPushIdThroughVariable message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_PUSH_ID_THROUGH_VARIABLE_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index dca142a..624eab3 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -58,6 +58,7 @@
           transformation_move_block_down_test.cpp
           transformation_outline_function_test.cpp
           transformation_permute_function_parameters_test.cpp
+          transformation_push_id_through_variable_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
           transformation_replace_id_with_synonym_test.cpp
diff --git a/test/fuzz/transformation_push_id_through_variable_test.cpp b/test/fuzz/transformation_push_id_through_variable_test.cpp
new file mode 100644
index 0000000..e00ee3a
--- /dev/null
+++ b/test/fuzz/transformation_push_id_through_variable_test.cpp
@@ -0,0 +1,446 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_push_id_through_variable.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPushIdThroughVariableTest, IsApplicable) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Tests the reference shader validity.
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Tests |value_synonym_id| and |variable_id| are fresh ids.
+  uint32_t value_id = 21;
+  uint32_t value_synonym_id = 62;
+  uint32_t variable_id = 63;
+  uint32_t variable_storage_class = SpvStorageClassPrivate;
+  auto instruction_descriptor =
+      MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  auto transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests |value_synonym_id| and |variable_id| are non-fresh ids.
+  value_id = 80;
+  value_synonym_id = 60;
+  variable_id = 61;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // The instruction to insert before is not defined.
+  value_id = 80;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(64, SpvOpAccessChain, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Attempting to insert the store and load instructions
+  // before an OpVariable instruction.
+  value_id = 24;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(27, SpvOpVariable, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // The block containing instruction descriptor must be reachable.
+  value_id = 80;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(100, SpvOpUnreachable, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests value instruction not available.
+  value_id = 64;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests pointer type not available.
+  value_id = 80;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassPrivate;
+  instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests not a private nor function storage class.
+  value_id = 93;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassInput;
+  instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      transformation.IsApplicable(context.get(), transformation_context),
+      "The variable storage class must be private or function");
+#endif
+
+  // Tests value instruction not available before instruction.
+  value_id = 95;
+  value_synonym_id = 62;
+  variable_id = 63;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(40, SpvOpAccessChain, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationPushIdThroughVariableTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  uint32_t value_id = 80;
+  uint32_t value_synonym_id = 100;
+  uint32_t variable_id = 101;
+  uint32_t variable_storage_class = SpvStorageClassFunction;
+  auto instruction_descriptor =
+      MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+  auto transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  value_id = 21;
+  value_synonym_id = 102;
+  variable_id = 103;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  value_id = 95;
+  value_synonym_id = 104;
+  variable_id = 105;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  value_id = 80;
+  value_synonym_id = 106;
+  variable_id = 107;
+  variable_storage_class = SpvStorageClassFunction;
+  instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  value_id = 21;
+  value_synonym_id = 108;
+  variable_id = 109;
+  variable_storage_class = SpvStorageClassPrivate;
+  instruction_descriptor = MakeInstructionDescriptor(95, SpvOpReturnValue, 0);
+  transformation = TransformationPushIdThroughVariable(
+      value_id, value_synonym_id, variable_id, variable_storage_class,
+      instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %60 = OpConstantNull %50
+         %61 = OpUndef %51
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+        %109 = OpVariable %51 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %103 = OpVariable %15 Function
+        %101 = OpVariable %9 Function
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+               OpStore %101 %80
+        %100 = OpLoad %8 %101
+               OpStore %103 %21
+        %102 = OpLoad %6 %103
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+        %107 = OpVariable %9 Function
+        %105 = OpVariable %9 Function
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpStore %105 %95
+        %104 = OpLoad %8 %105
+               OpStore %107 %80
+        %106 = OpLoad %8 %107
+               OpStore %109 %21
+        %108 = OpLoad %6 %109
+               OpReturnValue %21
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(80, {}),
+                                        MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
+                                        MakeDataDescriptor(102, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(95, {}),
+                                        MakeDataDescriptor(104, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(80, {}),
+                                        MakeDataDescriptor(106, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
+                                        MakeDataDescriptor(108, {})));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools