spirv-fuzz: Fuzzer passes to add local and global variables (#3175)

Adds two new fuzzer passes to add variables to a module: one that adds
Private storage class global variables, another that adds Function
storage class local variables.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index c816f87..b3c1970 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -40,6 +40,8 @@
         fuzzer_pass_add_dead_blocks.h
         fuzzer_pass_add_dead_breaks.h
         fuzzer_pass_add_dead_continues.h
+        fuzzer_pass_add_global_variables.h
+        fuzzer_pass_add_local_variables.h
         fuzzer_pass_add_no_contraction_decorations.h
         fuzzer_pass_add_useful_constructs.h
         fuzzer_pass_adjust_function_controls.h
@@ -74,6 +76,7 @@
         transformation_add_function.h
         transformation_add_global_undef.h
         transformation_add_global_variable.h
+        transformation_add_local_variable.h
         transformation_add_no_contraction_decoration.h
         transformation_add_type_array.h
         transformation_add_type_boolean.h
@@ -112,6 +115,8 @@
         fuzzer_pass_add_dead_blocks.cpp
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_dead_continues.cpp
+        fuzzer_pass_add_global_variables.cpp
+        fuzzer_pass_add_local_variables.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
         fuzzer_pass_add_useful_constructs.cpp
         fuzzer_pass_adjust_function_controls.cpp
@@ -145,6 +150,7 @@
         transformation_add_function.cpp
         transformation_add_global_undef.cpp
         transformation_add_global_variable.cpp
+        transformation_add_local_variable.cpp
         transformation_add_no_contraction_decoration.cpp
         transformation_add_type_array.cpp
         transformation_add_type_boolean.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 27b697c..8caa853 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -25,6 +25,8 @@
 #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"
+#include "source/fuzz/fuzzer_pass_add_global_variables.h"
+#include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
 #include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
 #include "source/fuzz/fuzzer_pass_adjust_function_controls.h"
@@ -191,6 +193,12 @@
     MaybeAddPass<FuzzerPassAddDeadContinues>(&passes, ir_context.get(),
                                              &fact_manager, &fuzzer_context,
                                              transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddGlobalVariables>(&passes, ir_context.get(),
+                                               &fact_manager, &fuzzer_context,
+                                               transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddLocalVariables>(&passes, ir_context.get(),
+                                              &fact_manager, &fuzzer_context,
+                                              transformation_sequence_out);
     MaybeAddPass<FuzzerPassApplyIdSynonyms>(&passes, ir_context.get(),
                                             &fact_manager, &fuzzer_context,
                                             transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 916803a..0fb2758 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -29,6 +29,8 @@
 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};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingGlobalVariable = {20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
     5, 70};
@@ -88,6 +90,10 @@
       ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
   chance_of_adding_dead_continue_ =
       ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+  chance_of_adding_global_variable_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
+  chance_of_adding_local_variable_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingLocalVariable);
   chance_of_adding_matrix_type_ =
       ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
   chance_of_adding_no_contraction_decoration_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index d4d6d58..2c48ac5 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -69,6 +69,12 @@
   uint32_t GetChanceOfAddingDeadContinue() {
     return chance_of_adding_dead_continue_;
   }
+  uint32_t GetChanceOfAddingGlobalVariable() {
+    return chance_of_adding_global_variable_;
+  }
+  uint32_t GetChanceOfAddingLocalVariable() {
+    return chance_of_adding_local_variable_;
+  }
   uint32_t GetChanceOfAddingMatrixType() {
     return chance_of_adding_matrix_type_;
   }
@@ -148,6 +154,8 @@
   uint32_t chance_of_adding_dead_block_;
   uint32_t chance_of_adding_dead_break_;
   uint32_t chance_of_adding_dead_continue_;
+  uint32_t chance_of_adding_global_variable_;
+  uint32_t chance_of_adding_local_variable_;
   uint32_t chance_of_adding_matrix_type_;
   uint32_t chance_of_adding_no_contraction_decoration_;
   uint32_t chance_of_adding_vector_type_;
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 9964a6c..40eb3bd 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -14,7 +14,10 @@
 
 #include "source/fuzz/fuzzer_pass.h"
 
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_add_constant_boolean.h"
+#include "source/fuzz/transformation_add_constant_composite.h"
 #include "source/fuzz/transformation_add_constant_scalar.h"
 #include "source/fuzz/transformation_add_global_undef.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
@@ -243,6 +246,42 @@
   return result;
 }
 
+uint32_t FuzzerPass::FindOrCreate32BitFloatConstant(uint32_t word) {
+  auto float_type_id = FindOrCreate32BitFloatType();
+  opt::analysis::FloatConstant float_constant(
+      GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(),
+      {word});
+  auto existing_constant =
+      GetIRContext()->get_constant_mgr()->FindConstant(&float_constant);
+  if (existing_constant) {
+    return GetIRContext()
+        ->get_constant_mgr()
+        ->GetDefiningInstruction(existing_constant)
+        ->result_id();
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddConstantScalar(result, float_type_id, {word}));
+  return result;
+}
+
+uint32_t FuzzerPass::FindOrCreateBoolConstant(bool value) {
+  auto bool_type_id = FindOrCreateBoolType();
+  opt::analysis::BoolConstant bool_constant(
+      GetIRContext()->get_type_mgr()->GetType(bool_type_id)->AsBool(), value);
+  auto existing_constant =
+      GetIRContext()->get_constant_mgr()->FindConstant(&bool_constant);
+  if (existing_constant) {
+    return GetIRContext()
+        ->get_constant_mgr()
+        ->GetDefiningInstruction(existing_constant)
+        ->result_id();
+  }
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddConstantBoolean(result, value));
+  return result;
+}
+
 uint32_t FuzzerPass::FindOrCreateGlobalUndef(uint32_t type_id) {
   for (auto& inst : GetIRContext()->types_values()) {
     if (inst.opcode() == SpvOpUndef && inst.type_id() == type_id) {
@@ -254,5 +293,147 @@
   return result;
 }
 
+std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
+FuzzerPass::GetAvailableBaseTypesAndPointers(
+    SpvStorageClass storage_class) const {
+  // Records all of the base types available in the module.
+  std::vector<uint32_t> base_types;
+
+  // For each base type, records all the associated pointer types that target
+  // that base type and that have |storage_class| as their storage class.
+  std::map<uint32_t, std::vector<uint32_t>> base_type_to_pointers;
+
+  for (auto& inst : GetIRContext()->types_values()) {
+    switch (inst.opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeBool:
+      case SpvOpTypeFloat:
+      case SpvOpTypeInt:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeStruct:
+      case SpvOpTypeVector:
+        // These types are suitable as pointer base types.  Record the type,
+        // and the fact that we cannot yet have seen any pointers that use this
+        // as its base type.
+        base_types.push_back(inst.result_id());
+        base_type_to_pointers.insert({inst.result_id(), {}});
+        break;
+      case SpvOpTypePointer:
+        if (inst.GetSingleWordInOperand(0) == storage_class) {
+          // The pointer has the desired storage class, so we are interested in
+          // it.  Associate it with its base type.
+          base_type_to_pointers.at(inst.GetSingleWordInOperand(1))
+              .push_back(inst.result_id());
+        }
+        break;
+      default:
+        break;
+    }
+  }
+  return {base_types, base_type_to_pointers};
+}
+
+uint32_t FuzzerPass::FindOrCreateZeroConstant(
+    uint32_t scalar_or_composite_type_id) {
+  auto type_instruction =
+      GetIRContext()->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
+  assert(type_instruction && "The type instruction must exist.");
+  switch (type_instruction->opcode()) {
+    case SpvOpTypeBool:
+      return FindOrCreateBoolConstant(false);
+    case SpvOpTypeFloat:
+      return FindOrCreate32BitFloatConstant(0);
+    case SpvOpTypeInt:
+      return FindOrCreate32BitIntegerConstant(
+          0, type_instruction->GetSingleWordInOperand(1) != 0);
+    case SpvOpTypeArray: {
+      return GetZeroConstantForHomogeneousComposite(
+          *type_instruction, type_instruction->GetSingleWordInOperand(0),
+          fuzzerutil::GetArraySize(*type_instruction, GetIRContext()));
+    }
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector: {
+      return GetZeroConstantForHomogeneousComposite(
+          *type_instruction, type_instruction->GetSingleWordInOperand(0),
+          type_instruction->GetSingleWordInOperand(1));
+    }
+    case SpvOpTypeStruct: {
+      std::vector<const opt::analysis::Constant*> field_zero_constants;
+      std::vector<uint32_t> field_zero_ids;
+      for (uint32_t index = 0; index < type_instruction->NumInOperands();
+           index++) {
+        uint32_t field_constant_id = FindOrCreateZeroConstant(
+            type_instruction->GetSingleWordInOperand(index));
+        field_zero_ids.push_back(field_constant_id);
+        field_zero_constants.push_back(
+            GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+                field_constant_id));
+      }
+      return FindOrCreateCompositeConstant(
+          *type_instruction, field_zero_constants, field_zero_ids);
+    }
+    default:
+      assert(false && "Unknown type.");
+      return 0;
+  }
+}
+
+uint32_t FuzzerPass::FindOrCreateCompositeConstant(
+    const opt::Instruction& composite_type_instruction,
+    const std::vector<const opt::analysis::Constant*>& constants,
+    const std::vector<uint32_t>& constant_ids) {
+  assert(constants.size() == constant_ids.size() &&
+         "Precondition: |constants| and |constant_ids| must be in "
+         "correspondence.");
+
+  opt::analysis::Type* composite_type = GetIRContext()->get_type_mgr()->GetType(
+      composite_type_instruction.result_id());
+  std::unique_ptr<opt::analysis::Constant> composite_constant;
+  if (composite_type->AsArray()) {
+    composite_constant = MakeUnique<opt::analysis::ArrayConstant>(
+        composite_type->AsArray(), constants);
+  } else if (composite_type->AsMatrix()) {
+    composite_constant = MakeUnique<opt::analysis::MatrixConstant>(
+        composite_type->AsMatrix(), constants);
+  } else if (composite_type->AsStruct()) {
+    composite_constant = MakeUnique<opt::analysis::StructConstant>(
+        composite_type->AsStruct(), constants);
+  } else if (composite_type->AsVector()) {
+    composite_constant = MakeUnique<opt::analysis::VectorConstant>(
+        composite_type->AsVector(), constants);
+  } else {
+    assert(false &&
+           "Precondition: |composite_type| must declare a composite type.");
+    return 0;
+  }
+
+  uint32_t existing_constant =
+      GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+          composite_constant.get(), composite_type_instruction.result_id());
+  if (existing_constant) {
+    return existing_constant;
+  }
+  uint32_t result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddConstantComposite(
+      result, composite_type_instruction.result_id(), constant_ids));
+  return result;
+}
+
+uint32_t FuzzerPass::GetZeroConstantForHomogeneousComposite(
+    const opt::Instruction& composite_type_instruction,
+    uint32_t component_type_id, uint32_t num_components) {
+  std::vector<const opt::analysis::Constant*> zero_constants;
+  std::vector<uint32_t> zero_ids;
+  uint32_t zero_component = FindOrCreateZeroConstant(component_type_id);
+  const opt::analysis::Constant* registered_zero_component =
+      GetIRContext()->get_constant_mgr()->FindDeclaredConstant(zero_component);
+  for (uint32_t i = 0; i < num_components; i++) {
+    zero_constants.push_back(registered_zero_component);
+    zero_ids.push_back(zero_component);
+  }
+  return FindOrCreateCompositeConstant(composite_type_instruction,
+                                       zero_constants, zero_ids);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index e1e8aec..4b78f29 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -138,12 +138,85 @@
   // applied to add them.
   uint32_t FindOrCreate32BitIntegerConstant(uint32_t word, bool is_signed);
 
+  // Returns the id of an OpConstant instruction, with 32-bit floating-point
+  // type, with |word| as its value.  If either the required floating-point type
+  // or the constant do not exist, transformations are applied to add them.
+  uint32_t FindOrCreate32BitFloatConstant(uint32_t word);
+
+  // Returns the id of an OpConstantTrue or OpConstantFalse instruction,
+  // according to |value|.  If either the required instruction or the bool
+  // type do not exist, transformations are applied to add them.
+  uint32_t FindOrCreateBoolConstant(bool value);
+
   // Returns the result id of an instruction of the form:
   //   %id = OpUndef %|type_id|
   // If no such instruction exists, a transformation is applied to add it.
   uint32_t FindOrCreateGlobalUndef(uint32_t type_id);
 
+  // Yields a pair, (base_type_ids, base_type_ids_to_pointers), such that:
+  // - base_type_ids captures every scalar or composite type declared in the
+  //   module (i.e., all int, bool, float, vector, matrix, struct and array
+  //   types
+  // - base_type_ids_to_pointers maps every such base type to the sequence
+  //   of all pointer types that have storage class |storage_class| and the
+  //   given base type as their pointee type.  The sequence may be empty for
+  //   some base types if no pointers to those types are defined for the given
+  //   storage class, and the sequence will have multiple elements if there are
+  //   repeated pointer declarations for the same base type and storage class.
+  std::pair<std::vector<uint32_t>, std::map<uint32_t, std::vector<uint32_t>>>
+  GetAvailableBaseTypesAndPointers(SpvStorageClass storage_class) const;
+
+  // Given a type id, |scalar_or_composite_type_id|, which must correspond to
+  // some scalar or composite type, returns the result id of an instruction
+  // defining a constant of the given type that is zero or false at everywhere.
+  // If such an instruction does not yet exist, transformations are applied to
+  // add it.
+  //
+  // Examples:
+  // --------------+-------------------------------
+  //   TYPE        | RESULT is id corresponding to
+  // --------------+-------------------------------
+  //   bool        | false
+  // --------------+-------------------------------
+  //   bvec4       | (false, false, false, false)
+  // --------------+-------------------------------
+  //   float       | 0.0
+  // --------------+-------------------------------
+  //   vec2        | (0.0, 0.0)
+  // --------------+-------------------------------
+  //   int[3]      | [0, 0, 0]
+  // --------------+-------------------------------
+  //   struct S {  |
+  //     int i;    | S(0, false, (0u, 0u))
+  //     bool b;   |
+  //     uint2 u;  |
+  //   }           |
+  // --------------+-------------------------------
+  uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id);
+
  private:
+  // Array, matrix and vector are *homogeneous* composite types in the sense
+  // that every component of one of these types has the same type.  Given a
+  // homogeneous composite type instruction, |composite_type_instruction|,
+  // returns the id of a composite constant instruction for which every element
+  // is zero/false.  If such an instruction does not yet exist, transformations
+  // are applied to add it.
+  uint32_t GetZeroConstantForHomogeneousComposite(
+      const opt::Instruction& composite_type_instruction,
+      uint32_t component_type_id, uint32_t num_components);
+
+  // Helper to find an existing composite constant instruction of the given
+  // composite type with the given constant components, or to apply
+  // transformations to create such an instruction if it does not yet exist.
+  // Parameter |composite_type_instruction| must be a composite type
+  // instruction.  The parameters |constants| and |constant_ids| must have the
+  // same size, and it must be the case that for each i, |constant_ids[i]| is
+  // the result id of an instruction that defines |constants[i]|.
+  uint32_t FindOrCreateCompositeConstant(
+      const opt::Instruction& composite_type_instruction,
+      const std::vector<const opt::analysis::Constant*>& constants,
+      const std::vector<uint32_t>& constant_ids);
+
   opt::IRContext* ir_context_;
   FactManager* fact_manager_;
   FuzzerContext* fuzzer_context_;
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.cpp b/source/fuzz/fuzzer_pass_add_global_variables.cpp
new file mode 100644
index 0000000..1371f46
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_global_variables.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_global_variables.h"
+
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddGlobalVariables::FuzzerPassAddGlobalVariables(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default;
+
+void FuzzerPassAddGlobalVariables::Apply() {
+  auto base_type_ids_and_pointers =
+      GetAvailableBaseTypesAndPointers(SpvStorageClassPrivate);
+
+  // These are the base types that are available to this fuzzer pass.
+  auto& base_types = base_type_ids_and_pointers.first;
+
+  // These are the pointers to those base types that are *initially* available
+  // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
+  // none are available for a given base type.
+  auto& base_type_to_pointers = base_type_ids_and_pointers.second;
+
+  // Probabilistically keep adding global variables.
+  while (GetFuzzerContext()->ChoosePercentage(
+      GetFuzzerContext()->GetChanceOfAddingGlobalVariable())) {
+    // Choose a random base type; the new variable's type will be a pointer to
+    // this base type.
+    uint32_t base_type =
+        base_types[GetFuzzerContext()->RandomIndex(base_types)];
+    uint32_t pointer_type_id;
+    std::vector<uint32_t>& available_pointers_to_base_type =
+        base_type_to_pointers.at(base_type);
+    // Determine whether there is at least one pointer to this base type.
+    if (available_pointers_to_base_type.empty()) {
+      // There is not.  Make one, to use here, and add it to the available
+      // pointers for the base type so that future variables can potentially
+      // use it.
+      pointer_type_id = GetFuzzerContext()->GetFreshId();
+      available_pointers_to_base_type.push_back(pointer_type_id);
+      ApplyTransformation(TransformationAddTypePointer(
+          pointer_type_id, SpvStorageClassPrivate, base_type));
+    } else {
+      // There is - grab one.
+      pointer_type_id =
+          available_pointers_to_base_type[GetFuzzerContext()->RandomIndex(
+              available_pointers_to_base_type)];
+    }
+    ApplyTransformation(TransformationAddGlobalVariable(
+        GetFuzzerContext()->GetFreshId(), pointer_type_id,
+        FindOrCreateZeroConstant(base_type), true));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.h b/source/fuzz/fuzzer_pass_add_global_variables.h
new file mode 100644
index 0000000..c71d147
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_global_variables.h
@@ -0,0 +1,40 @@
+// 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 SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds global variables, with Private storage class,
+// to the module.
+class FuzzerPassAddGlobalVariables : public FuzzerPass {
+ public:
+  FuzzerPassAddGlobalVariables(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddGlobalVariables();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_GLOBAL_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.cpp b/source/fuzz/fuzzer_pass_add_local_variables.cpp
new file mode 100644
index 0000000..8d6d80d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_local_variables.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_local_variables.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_local_variable.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddLocalVariables::FuzzerPassAddLocalVariables(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default;
+
+void FuzzerPassAddLocalVariables::Apply() {
+  auto base_type_ids_and_pointers =
+      GetAvailableBaseTypesAndPointers(SpvStorageClassFunction);
+
+  // These are the base types that are available to this fuzzer pass.
+  auto& base_types = base_type_ids_and_pointers.first;
+
+  // These are the pointers to those base types that are *initially* available
+  // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
+  // none are available for a given base type.
+  auto& base_type_to_pointers = base_type_ids_and_pointers.second;
+
+  // Consider every function in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    // Probabilistically keep adding random variables to this function.
+    while (GetFuzzerContext()->ChoosePercentage(
+        GetFuzzerContext()->GetChanceOfAddingLocalVariable())) {
+      // Choose a random base type; the new variable's type will be a pointer to
+      // this base type.
+      uint32_t base_type =
+          base_types[GetFuzzerContext()->RandomIndex(base_types)];
+      uint32_t pointer_type;
+      std::vector<uint32_t>& available_pointers_to_base_type =
+          base_type_to_pointers.at(base_type);
+      // Determine whether there is at least one pointer to this base type.
+      if (available_pointers_to_base_type.empty()) {
+        // There is not.  Make one, to use here, and add it to the available
+        // pointers for the base type so that future variables can potentially
+        // use it.
+        pointer_type = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationAddTypePointer(
+            pointer_type, SpvStorageClassFunction, base_type));
+        available_pointers_to_base_type.push_back(pointer_type);
+      } else {
+        // There is - grab one.
+        pointer_type =
+            available_pointers_to_base_type[GetFuzzerContext()->RandomIndex(
+                available_pointers_to_base_type)];
+      }
+      ApplyTransformation(TransformationAddLocalVariable(
+          GetFuzzerContext()->GetFreshId(), pointer_type, function.result_id(),
+          FindOrCreateZeroConstant(base_type), true));
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.h b/source/fuzz/fuzzer_pass_add_local_variables.h
new file mode 100644
index 0000000..ef002fb
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_local_variables.h
@@ -0,0 +1,43 @@
+// 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 SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+#include <utility>
+#include <vector>
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds local variables, with Function storage class,
+// to the module.
+class FuzzerPassAddLocalVariables : public FuzzerPass {
+ public:
+  FuzzerPassAddLocalVariables(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddLocalVariables();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_LOCAL_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index f9f9969..b81b17d 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -391,6 +391,15 @@
   return 0;
 }
 
+opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) {
+  for (auto& function : *ir_context->module()) {
+    if (function.result_id() == function_id) {
+      return &function;
+    }
+  }
+  return nullptr;
+}
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index f0a2953..1cbc59f 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -137,6 +137,10 @@
 uint32_t FindFunctionType(opt::IRContext* ir_context,
                           const std::vector<uint32_t>& type_ids);
 
+// Returns the function with result id |function_id|, or |nullptr| if no such
+// function exists.
+opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 52a3a78..67b362a 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -339,6 +339,7 @@
     TransformationAddGlobalUndef add_global_undef = 32;
     TransformationAddFunction add_function = 33;
     TransformationAddDeadBlock add_dead_block = 34;
+    TransformationAddLocalVariable add_local_variable = 35;
     // Add additional option using the next available number.
   }
 }
@@ -507,15 +508,38 @@
   // Optional initializer; 0 if there is no initializer
   uint32 initializer_id = 3;
 
-  // True if and only if the value of the variable should be regarded, in
-  // general, as totally unknown and subject to change (even if, due to an
-  // initializer, the original value is known).  This is the case for variables
-  // added when a module is donated, for example, and means that stores to such
-  // variables can be performed in an arbitrary fashion.
+  // True if and only if the behaviour of the module should not depend on the
+  // value of the variable, in which case stores to the variable can be
+  // performed in an arbitrary fashion.
   bool value_is_arbitrary = 4;
 
 }
 
+message TransformationAddLocalVariable {
+
+  // Adds a local variable of the given type (which must be a pointer with
+  // Function storage class) to the given function, initialized to the given
+  // id.
+
+  // Fresh id for the local variable
+  uint32 fresh_id = 1;
+
+  // The type of the local variable
+  uint32 type_id = 2;
+
+  // The id of the function to which the local variable should be added
+  uint32 function_id = 3;
+
+  // Initial value of the variable
+  uint32 initializer_id = 4;
+
+  // True if and only if the behaviour of the module should not depend on the
+  // value of the variable, in which case stores to the variable can be
+  // performed in an arbitrary fashion.
+  bool value_is_arbitrary = 5;
+
+}
+
 message TransformationAddNoContractionDecoration {
 
   // Applies OpDecorate NoContraction to the given result id
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 8037af1..1ed4318 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -26,6 +26,7 @@
 #include "source/fuzz/transformation_add_function.h"
 #include "source/fuzz/transformation_add_global_undef.h"
 #include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
 #include "source/fuzz/transformation_add_type_array.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
@@ -85,6 +86,9 @@
     case protobufs::Transformation::TransformationCase::kAddGlobalVariable:
       return MakeUnique<TransformationAddGlobalVariable>(
           message.add_global_variable());
+    case protobufs::Transformation::TransformationCase::kAddLocalVariable:
+      return MakeUnique<TransformationAddLocalVariable>(
+          message.add_local_variable());
     case protobufs::Transformation::TransformationCase::
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp
new file mode 100644
index 0000000..cdaea53
--- /dev/null
+++ b/source/fuzz/transformation_add_local_variable.cpp
@@ -0,0 +1,98 @@
+// 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_add_local_variable.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddLocalVariable::TransformationAddLocalVariable(
+    const spvtools::fuzz::protobufs::TransformationAddLocalVariable& message)
+    : message_(message) {}
+
+TransformationAddLocalVariable::TransformationAddLocalVariable(
+    uint32_t fresh_id, uint32_t type_id, uint32_t function_id,
+    uint32_t initializer_id, bool value_is_arbitrary) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_type_id(type_id);
+  message_.set_function_id(function_id);
+  message_.set_initializer_id(initializer_id);
+  message_.set_value_is_arbitrary(value_is_arbitrary);
+}
+
+bool TransformationAddLocalVariable::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The provided id must be fresh.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+  // The pointer type id must indeed correspond to a pointer, and it must have
+  // function storage class.
+  auto type_instruction =
+      context->get_def_use_mgr()->GetDef(message_.type_id());
+  if (!type_instruction || type_instruction->opcode() != SpvOpTypePointer ||
+      type_instruction->GetSingleWordInOperand(0) != SpvStorageClassFunction) {
+    return false;
+  }
+  // The initializer must...
+  auto initializer_instruction =
+      context->get_def_use_mgr()->GetDef(message_.initializer_id());
+  // ... exist, ...
+  if (!initializer_instruction) {
+    return false;
+  }
+  // ... be a constant, ...
+  if (!spvOpcodeIsConstant(initializer_instruction->opcode())) {
+    return false;
+  }
+  // ... and have the same type as the pointee type.
+  if (initializer_instruction->type_id() !=
+      type_instruction->GetSingleWordInOperand(1)) {
+    return false;
+  }
+  // The function to which the local variable is to be added must exist.
+  return fuzzerutil::FindFunction(context, message_.function_id());
+}
+
+void TransformationAddLocalVariable::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  fuzzerutil::FindFunction(context, message_.function_id())
+      ->begin()
+      ->begin()
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          context, SpvOpVariable, message_.type_id(), message_.fresh_id(),
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+                {
+
+                    SpvStorageClassFunction}},
+               {SPV_OPERAND_TYPE_ID, {message_.initializer_id()}}})));
+  if (message_.value_is_arbitrary()) {
+    fact_manager->AddFactValueOfVariableIsArbitrary(message_.fresh_id());
+  }
+  context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddLocalVariable::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_local_variable() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_local_variable.h b/source/fuzz/transformation_add_local_variable.h
new file mode 100644
index 0000000..6a97b71
--- /dev/null
+++ b/source/fuzz/transformation_add_local_variable.h
@@ -0,0 +1,60 @@
+// 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 SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddLocalVariable : public Transformation {
+ public:
+  explicit TransformationAddLocalVariable(
+      const protobufs::TransformationAddLocalVariable& message);
+
+  TransformationAddLocalVariable(uint32_t fresh_id, uint32_t type_id,
+                                 uint32_t function_id, uint32_t initializer_id,
+                                 bool value_is_arbitrary);
+
+  // - |message_.fresh_id| must not be used by the module
+  // - |message_.type_id| must be the id of a pointer type with Function
+  //   storage class
+  // - |message_.initializer_id| must be the id of a constant with the same
+  //   type as the pointer's pointee type
+  // - |message_.function_id| must be the id of a function
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Adds an instruction to the start of |message_.function_id|, of the form:
+  //   |message_.fresh_id| = OpVariable |message_.type_id| Function
+  //                         |message_.initializer_id|
+  // If |message_.value_is_arbitrary| holds, adds a corresponding fact to
+  // |fact_manager|.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddLocalVariable message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_LOCAL_VARIABLE_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 732d9fe..d371326 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -33,6 +33,7 @@
           transformation_add_function_test.cpp
           transformation_add_global_undef_test.cpp
           transformation_add_global_variable_test.cpp
+          transformation_add_local_variable_test.cpp
           transformation_add_no_contraction_decoration_test.cpp
           transformation_add_type_array_test.cpp
           transformation_add_type_boolean_test.cpp
diff --git a/test/fuzz/transformation_add_local_variable_test.cpp b/test/fuzz/transformation_add_local_variable_test.cpp
new file mode 100644
index 0000000..465af41
--- /dev/null
+++ b/test/fuzz/transformation_add_local_variable_test.cpp
@@ -0,0 +1,206 @@
+// 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_add_local_variable.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLocalVariableTest, 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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeFloat 32
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 3
+         %16 = OpTypeArray %13 %15
+         %17 = OpTypeBool
+         %18 = OpTypeStruct %16 %7 %17
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %13 1
+         %22 = OpConstant %13 2
+         %23 = OpConstant %13 4
+         %24 = OpConstantComposite %16 %21 %22 %23
+         %25 = OpConstant %6 5
+         %26 = OpConstant %6 6
+         %27 = OpConstantComposite %7 %25 %26
+         %28 = OpConstantFalse %17
+         %29 = OpConstantComposite %18 %24 %27 %28
+         %30 = OpTypeVector %13 2
+         %31 = OpTypePointer Function %30
+         %33 = OpConstantComposite %30 %21 %21
+         %34 = OpTypeVector %17 3
+         %35 = OpTypePointer Function %34
+         %37 = OpConstantTrue %17
+         %38 = OpConstantComposite %34 %37 %28 %28
+         %39 = OpTypeVector %13 4
+         %40 = OpTypeMatrix %39 3
+         %41 = OpTypePointer Function %40
+         %43 = OpConstantComposite %39 %21 %22 %23 %21
+         %44 = OpConstantComposite %39 %22 %23 %21 %22
+         %45 = OpConstantComposite %39 %23 %21 %22 %23
+         %46 = OpConstantComposite %40 %43 %44 %45
+         %50 = OpTypePointer Function %14
+         %51 = OpConstantNull %14
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // A few cases of inapplicable transformations:
+  // Id 4 is already in use
+  ASSERT_FALSE(TransformationAddLocalVariable(4, 50, 4, 51, true)
+                   .IsApplicable(context.get(), fact_manager));
+  // Type mismatch between initializer and pointer
+  ASSERT_FALSE(TransformationAddLocalVariable(105, 46, 4, 51, true)
+                   .IsApplicable(context.get(), fact_manager));
+  // Id 5 is not a function
+  ASSERT_FALSE(TransformationAddLocalVariable(105, 50, 5, 51, true)
+                   .IsApplicable(context.get(), fact_manager));
+
+  // %105 = OpVariable %50 Function %51
+  {
+    TransformationAddLocalVariable transformation(105, 50, 4, 51, true);
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  // %104 = OpVariable %41 Function %46
+  {
+    TransformationAddLocalVariable transformation(104, 41, 4, 46, false);
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  // %103 = OpVariable %35 Function %38
+  {
+    TransformationAddLocalVariable transformation(103, 35, 4, 38, true);
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  // %102 = OpVariable %31 Function %33
+  {
+    TransformationAddLocalVariable transformation(102, 31, 4, 33, false);
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  // %101 = OpVariable %19 Function %29
+  {
+    TransformationAddLocalVariable transformation(101, 19, 4, 29, true);
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  // %100 = OpVariable %8 Function %12
+  {
+    TransformationAddLocalVariable transformation(100, 8, 4, 12, false);
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+  }
+
+  ASSERT_FALSE(fact_manager.VariableValueIsArbitrary(100));
+  ASSERT_TRUE(fact_manager.VariableValueIsArbitrary(101));
+  ASSERT_FALSE(fact_manager.VariableValueIsArbitrary(102));
+  ASSERT_TRUE(fact_manager.VariableValueIsArbitrary(103));
+  ASSERT_FALSE(fact_manager.VariableValueIsArbitrary(104));
+  ASSERT_TRUE(fact_manager.VariableValueIsArbitrary(105));
+
+  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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeFloat 32
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 3
+         %16 = OpTypeArray %13 %15
+         %17 = OpTypeBool
+         %18 = OpTypeStruct %16 %7 %17
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %13 1
+         %22 = OpConstant %13 2
+         %23 = OpConstant %13 4
+         %24 = OpConstantComposite %16 %21 %22 %23
+         %25 = OpConstant %6 5
+         %26 = OpConstant %6 6
+         %27 = OpConstantComposite %7 %25 %26
+         %28 = OpConstantFalse %17
+         %29 = OpConstantComposite %18 %24 %27 %28
+         %30 = OpTypeVector %13 2
+         %31 = OpTypePointer Function %30
+         %33 = OpConstantComposite %30 %21 %21
+         %34 = OpTypeVector %17 3
+         %35 = OpTypePointer Function %34
+         %37 = OpConstantTrue %17
+         %38 = OpConstantComposite %34 %37 %28 %28
+         %39 = OpTypeVector %13 4
+         %40 = OpTypeMatrix %39 3
+         %41 = OpTypePointer Function %40
+         %43 = OpConstantComposite %39 %21 %22 %23 %21
+         %44 = OpConstantComposite %39 %22 %23 %21 %22
+         %45 = OpConstantComposite %39 %23 %21 %22 %23
+         %46 = OpConstantComposite %40 %43 %44 %45
+         %50 = OpTypePointer Function %14
+         %51 = OpConstantNull %14
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %100 = OpVariable %8 Function %12
+        %101 = OpVariable %19 Function %29
+        %102 = OpVariable %31 Function %33
+        %103 = OpVariable %35 Function %38
+        %104 = OpVariable %41 Function %46
+        %105 = OpVariable %50 Function %51
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools