Add fuzzer pass to obfuscate constants. (#2671)

Adds a new transformation that can replace a constant with a uniform known to have the same value, and adds a fuzzer pass that (a) replaces a boolean with a comparison of literals (e.g. replacing "true" with "42 > 24"), and then (b) obfuscates the literals appearing in this comparison by replacing them with identically-valued uniforms, if available.

The fuzzer_replayer test file has also been updated to allow initial facts to be provided, and to do error checking of the status results returned by the fuzzer and replayer components.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 9121a53..a12e4f4 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -32,6 +32,7 @@
         fuzzer_pass.h
         fuzzer_pass_add_dead_breaks.h
         fuzzer_pass_add_useful_constructs.h
+        fuzzer_pass_obfuscate_constants.h
         fuzzer_pass_permute_blocks.h
         fuzzer_pass_split_blocks.h
         fuzzer_util.h
@@ -46,8 +47,10 @@
         transformation_add_type_boolean.h
         transformation_add_type_float.h
         transformation_add_type_int.h
+        transformation_add_type_pointer.h
         transformation_move_block_down.h
         transformation_replace_boolean_constant_with_constant_binary.h
+        transformation_replace_constant_with_uniform.h
         transformation_split_block.h
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
@@ -58,6 +61,7 @@
         fuzzer_pass.cpp
         fuzzer_pass_add_dead_breaks.cpp
         fuzzer_pass_add_useful_constructs.cpp
+        fuzzer_pass_obfuscate_constants.cpp
         fuzzer_pass_permute_blocks.cpp
         fuzzer_pass_split_blocks.cpp
         fuzzer_util.cpp
@@ -71,8 +75,10 @@
         transformation_add_type_boolean.cpp
         transformation_add_type_float.cpp
         transformation_add_type_int.cpp
+        transformation_add_type_pointer.cpp
         transformation_move_block_down.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
+        transformation_replace_constant_with_uniform.cpp
         transformation_split_block.cpp
         uniform_buffer_element_descriptor.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
index 3716941..407881b 100644
--- a/source/fuzz/fact_manager.cpp
+++ b/source/fuzz/fact_manager.cpp
@@ -64,6 +64,11 @@
       const protobufs::FactConstantUniform& constant_uniform_fact,
       uint32_t type_id) const;
 
+  // Checks that the width of a floating-point constant is supported, and that
+  // the constant is finite.
+  bool FloatingPointValueIsSuitable(const protobufs::FactConstantUniform& fact,
+                                    uint32_t width) const;
+
   std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
       facts_and_type_ids;
 };
@@ -169,6 +174,31 @@
   return result;
 }
 
+bool FactManager::ConstantUniformFacts::FloatingPointValueIsSuitable(
+    const protobufs::FactConstantUniform& fact, uint32_t width) const {
+  const uint32_t kFloatWidth = 32;
+  const uint32_t kDoubleWidth = 64;
+  if (width != kFloatWidth && width != kDoubleWidth) {
+    // Only 32- and 64-bit floating-point types are handled.
+    return false;
+  }
+  std::vector<uint32_t> words = GetConstantWords(fact);
+  if (width == 32) {
+    float value;
+    memcpy(&value, words.data(), sizeof(float));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  } else {
+    double value;
+    memcpy(&value, words.data(), sizeof(double));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool FactManager::ConstantUniformFacts::AddFact(
     const protobufs::FactConstantUniform& fact, opt::IRContext* context) {
   auto should_be_uniform_variable = context->get_def_use_mgr()->GetDef(
@@ -236,6 +266,12 @@
   auto width = final_element_type->AsFloat()
                    ? final_element_type->AsFloat()->width()
                    : final_element_type->AsInteger()->width();
+
+  if (final_element_type->AsFloat() &&
+      !FloatingPointValueIsSuitable(fact, width)) {
+    return false;
+  }
+
   auto required_words = (width + 32 - 1) / 32;
   if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
     return false;
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 25c24fc..42033e2 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -21,6 +21,7 @@
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
 #include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
 #include "source/fuzz/fuzzer_pass_permute_blocks.h"
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
@@ -113,8 +114,9 @@
   FuzzerPassAddDeadBreaks(ir_context.get(), &fact_manager, &fuzzer_context,
                           transformation_sequence_out)
       .Apply();
-
-  // TODO(afd) Various other passes will be added.
+  FuzzerPassObfuscateConstants(ir_context.get(), &fact_manager, &fuzzer_context,
+                               transformation_sequence_out)
+      .Apply();
 
   // Finally, give the blocks in the module a good shake-up.
   FuzzerPassPermuteBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index b50dfab..9252341 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/fuzzer_context.h"
 
+#include <cmath>
+
 namespace spvtools {
 namespace fuzz {
 
@@ -24,8 +26,19 @@
 
 const uint32_t kDefaultChanceOfAddingDeadBreak = 20;
 const uint32_t kDefaultChanceOfMovingBlockDown = 25;
+const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
 const uint32_t kDefaultChanceOfSplittingBlock = 20;
 
+// Default functions for controlling how deep to go during recursive
+// generation/transformation. Keep them in alphabetical order.
+
+const std::function<bool(uint32_t, RandomGenerator*)>
+    kDefaultGoDeeperInConstantObfuscation =
+        [](uint32_t current_depth, RandomGenerator* random_generator) -> bool {
+  double chance = 1.0 / std::pow(3.0, static_cast<float>(current_depth + 1));
+  return random_generator->RandomDouble() < chance;
+};
+
 }  // namespace
 
 FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
@@ -34,7 +47,10 @@
       next_fresh_id_(min_fresh_id),
       chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak),
       chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
-      chance_of_splitting_block_(kDefaultChanceOfSplittingBlock) {}
+      chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
+      chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
+      go_deeper_in_constant_obfuscation_(
+          kDefaultGoDeeperInConstantObfuscation) {}
 
 FuzzerContext::~FuzzerContext() = default;
 
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 936ffbc..2815bf7 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -15,6 +15,8 @@
 #ifndef SOURCE_FUZZ_FUZZER_CONTEXT_H_
 #define SOURCE_FUZZ_FUZZER_CONTEXT_H_
 
+#include <functional>
+
 #include "source/fuzz/random_generator.h"
 #include "source/opt/function.h"
 
@@ -43,8 +45,18 @@
   // Keep them in alphabetical order.
   uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; }
   uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
+  uint32_t GetChanceOfObfuscatingConstant() {
+    return chance_of_obfuscating_constant_;
+  }
   uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
 
+  // Probability distributions to control how deeply to recurse.
+  // Keep them in alphabetical order.
+  const std::function<bool(uint32_t, RandomGenerator*)>&
+  GoDeeperInConstantObfuscation() {
+    return go_deeper_in_constant_obfuscation_;
+  }
+
  private:
   // The source of randomness.
   RandomGenerator* random_generator_;
@@ -55,7 +67,13 @@
   // Keep them in alphabetical order.
   uint32_t chance_of_adding_dead_break_;
   uint32_t chance_of_moving_block_down_;
+  uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_splitting_block_;
+
+  // Functions to determine with what probability to go deeper when generating
+  // or mutating constructs recursively.
+  const std::function<bool(uint32_t, RandomGenerator*)>&
+      go_deeper_in_constant_obfuscation_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
index 335e3ca..23606de 100644
--- a/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
+++ b/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
@@ -19,6 +19,7 @@
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
 #include "source/fuzz/transformation_add_type_int.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -176,6 +177,55 @@
   for (unsigned int& datum : uint_data) {
     MaybeAddFloatConstant(32, {datum});
   }
+
+  // For every known-to-be-constant uniform, make sure we have instructions
+  // declaring:
+  // - a pointer type with uniform storage class, whose pointee type is the type
+  //   of the element
+  // - a signed integer constant for each index required to access the element
+  // - a constant for the constant value itself
+  for (auto& fact_and_type_id :
+       GetFactManager()->GetConstantUniformFactsAndTypes()) {
+    uint32_t element_type_id = fact_and_type_id.second;
+    assert(element_type_id);
+    auto element_type =
+        GetIRContext()->get_type_mgr()->GetType(element_type_id);
+    assert(element_type &&
+           "If the constant uniform fact is well-formed, the module must "
+           "already have a declaration of the type for the uniform element.");
+    opt::analysis::Pointer uniform_pointer(element_type,
+                                           SpvStorageClassUniform);
+    if (!GetIRContext()->get_type_mgr()->GetId(&uniform_pointer)) {
+      auto add_pointer = transformation::MakeTransformationAddTypePointer(
+          GetFuzzerContext()->GetFreshId(), SpvStorageClassUniform,
+          element_type_id);
+      assert(transformation::IsApplicable(add_pointer, GetIRContext(),
+                                          *GetFactManager()) &&
+             "Should be applicable by construction.");
+      transformation::Apply(add_pointer, GetIRContext(), GetFactManager());
+      *GetTransformations()->add_transformation()->mutable_add_type_pointer() =
+          add_pointer;
+    }
+    std::vector<uint32_t> words;
+    for (auto word : fact_and_type_id.first.constant_word()) {
+      words.push_back(word);
+    }
+    // We get the element type again as the type manager may have been
+    // invalidated since we last retrieved it.
+    element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
+    if (element_type->AsInteger()) {
+      MaybeAddIntConstant(element_type->AsInteger()->width(),
+                          element_type->AsInteger()->IsSigned(), words);
+    } else {
+      assert(element_type->AsFloat() &&
+             "Known uniform values must be integer or floating-point.");
+      MaybeAddFloatConstant(element_type->AsFloat()->width(), words);
+    }
+    for (auto index :
+         fact_and_type_id.first.uniform_buffer_element_descriptor().index()) {
+      MaybeAddIntConstant(32, true, {index});
+    }
+  }
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
new file mode 100644
index 0000000..36fcb8b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
@@ -0,0 +1,471 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
+
+#include <cmath>
+
+#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassObfuscateConstants::FuzzerPassObfuscateConstants(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassObfuscateConstants::~FuzzerPassObfuscateConstants() = default;
+
+void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
+    uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+    const std::vector<SpvOp>& greater_than_opcodes,
+    const std::vector<SpvOp>& less_than_opcodes, uint32_t constant_id_1,
+    uint32_t constant_id_2, bool first_constant_is_larger) {
+  auto bool_constant_opcode = GetIRContext()
+                                  ->get_def_use_mgr()
+                                  ->GetDef(bool_constant_use.id_of_interest())
+                                  ->opcode();
+  assert((bool_constant_opcode == SpvOpConstantFalse ||
+          bool_constant_opcode == SpvOpConstantTrue) &&
+         "Precondition: this must be a usage of a boolean constant.");
+
+  // Pick an opcode at random.  First randomly decide whether to generate
+  // a 'greater than' or 'less than' kind of opcode, and then select a
+  // random opcode from the resulting subset.
+  SpvOp comparison_opcode;
+  if (GetFuzzerContext()->GetRandomGenerator()->RandomBool()) {
+    comparison_opcode = greater_than_opcodes
+        [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+            static_cast<uint32_t>(greater_than_opcodes.size()))];
+  } else {
+    comparison_opcode = less_than_opcodes
+        [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+            static_cast<uint32_t>(less_than_opcodes.size()))];
+  }
+
+  // We now need to decide how to order constant_id_1 and constant_id_2 such
+  // that 'constant_id_1 comparison_opcode constant_id_2' evaluates to the
+  // boolean constant.
+  const bool is_greater_than_opcode =
+      std::find(greater_than_opcodes.begin(), greater_than_opcodes.end(),
+                comparison_opcode) != greater_than_opcodes.end();
+  uint32_t lhs_id;
+  uint32_t rhs_id;
+  if ((bool_constant_opcode == SpvOpConstantTrue &&
+       first_constant_is_larger == is_greater_than_opcode) ||
+      (bool_constant_opcode == SpvOpConstantFalse &&
+       first_constant_is_larger != is_greater_than_opcode)) {
+    lhs_id = constant_id_1;
+    rhs_id = constant_id_2;
+  } else {
+    lhs_id = constant_id_2;
+    rhs_id = constant_id_1;
+  }
+
+  // We can now make a transformation that will replace |bool_constant_use|
+  // with an expression of the form (written using infix notation):
+  // |lhs_id| |comparison_opcode| |rhs_id|
+  auto transformation = transformation::
+      MakeTransformationReplaceBooleanConstantWithConstantBinary(
+          bool_constant_use, lhs_id, rhs_id, comparison_opcode,
+          GetFuzzerContext()->GetFreshId());
+  // The transformation should be applicable by construction.
+  assert(transformation::IsApplicable(transformation, GetIRContext(),
+                                      *GetFactManager()));
+
+  // Applying this transformation yields a pointer to the new instruction that
+  // computes the result of the binary expression.
+  auto binary_operator_instruction =
+      transformation::Apply(transformation, GetIRContext(), GetFactManager());
+
+  // Add this transformation to the sequence of transformations that have been
+  // applied.
+  *GetTransformations()
+       ->add_transformation()
+       ->mutable_replace_boolean_constant_with_constant_binary() =
+      transformation;
+
+  // Having made a binary expression, there may now be opportunities to further
+  // obfuscate the constants used as the LHS and RHS of the expression (e.g. by
+  // replacing them with loads from known uniforms).
+  //
+  // We thus consider operands 0 and 1 (LHS and RHS in turn).
+  for (uint32_t index : {0u, 1u}) {
+    // We randomly decide, based on the current depth of obfuscation, whether
+    // to further obfuscate this operand.
+    if (GetFuzzerContext()->GoDeeperInConstantObfuscation()(
+            depth, GetFuzzerContext()->GetRandomGenerator())) {
+      auto in_operand_use = transformation::MakeIdUseDescriptor(
+          binary_operator_instruction->GetSingleWordInOperand(index),
+          binary_operator_instruction->opcode(), index,
+          binary_operator_instruction->result_id(), 0);
+      ObfuscateConstant(depth + 1, in_operand_use);
+    }
+  }
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaFloatConstantPair(
+    uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+    uint32_t float_constant_id_1, uint32_t float_constant_id_2) {
+  auto float_constant_1 = GetIRContext()
+                              ->get_constant_mgr()
+                              ->FindDeclaredConstant(float_constant_id_1)
+                              ->AsFloatConstant();
+  auto float_constant_2 = GetIRContext()
+                              ->get_constant_mgr()
+                              ->FindDeclaredConstant(float_constant_id_2)
+                              ->AsFloatConstant();
+  assert(float_constant_1->words() != float_constant_2->words() &&
+         "The constants should not be identical.");
+  assert(std::isfinite(float_constant_1->GetValueAsDouble()) &&
+         "The constants must be finite numbers.");
+  assert(std::isfinite(float_constant_2->GetValueAsDouble()) &&
+         "The constants must be finite numbers.");
+  bool first_constant_is_larger;
+  assert(float_constant_1->type()->AsFloat()->width() ==
+             float_constant_2->type()->AsFloat()->width() &&
+         "First and second floating-point constants must have the same width.");
+  if (float_constant_1->type()->AsFloat()->width() == 32) {
+    first_constant_is_larger =
+        float_constant_1->GetFloat() > float_constant_2->GetFloat();
+  } else {
+    assert(float_constant_1->type()->AsFloat()->width() == 64 &&
+           "Supported floating-point widths are 32 and 64.");
+    first_constant_is_larger =
+        float_constant_1->GetDouble() > float_constant_2->GetDouble();
+  }
+  std::vector<SpvOp> greater_than_opcodes{
+      SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
+      SpvOpFUnordGreaterThanEqual};
+  std::vector<SpvOp> less_than_opcodes{
+      SpvOpFOrdGreaterThan, SpvOpFOrdGreaterThanEqual, SpvOpFUnordGreaterThan,
+      SpvOpFUnordGreaterThanEqual};
+
+  ObfuscateBoolConstantViaConstantPair(
+      depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
+      float_constant_id_1, float_constant_id_2, first_constant_is_larger);
+}
+
+void FuzzerPassObfuscateConstants::
+    ObfuscateBoolConstantViaSignedIntConstantPair(
+        uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+        uint32_t signed_int_constant_id_1, uint32_t signed_int_constant_id_2) {
+  auto signed_int_constant_1 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(signed_int_constant_id_1)
+          ->AsIntConstant();
+  auto signed_int_constant_2 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(signed_int_constant_id_2)
+          ->AsIntConstant();
+  assert(signed_int_constant_1->words() != signed_int_constant_2->words() &&
+         "The constants should not be identical.");
+  bool first_constant_is_larger;
+  assert(signed_int_constant_1->type()->AsInteger()->width() ==
+             signed_int_constant_2->type()->AsInteger()->width() &&
+         "First and second floating-point constants must have the same width.");
+  assert(signed_int_constant_1->type()->AsInteger()->IsSigned());
+  assert(signed_int_constant_2->type()->AsInteger()->IsSigned());
+  if (signed_int_constant_1->type()->AsFloat()->width() == 32) {
+    first_constant_is_larger =
+        signed_int_constant_1->GetS32() > signed_int_constant_2->GetS32();
+  } else {
+    assert(signed_int_constant_1->type()->AsFloat()->width() == 64 &&
+           "Supported integer widths are 32 and 64.");
+    first_constant_is_larger =
+        signed_int_constant_1->GetS64() > signed_int_constant_2->GetS64();
+  }
+  std::vector<SpvOp> greater_than_opcodes{SpvOpSGreaterThan,
+                                          SpvOpSGreaterThanEqual};
+  std::vector<SpvOp> less_than_opcodes{SpvOpSLessThan, SpvOpSLessThanEqual};
+
+  ObfuscateBoolConstantViaConstantPair(
+      depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
+      signed_int_constant_id_1, signed_int_constant_id_2,
+      first_constant_is_larger);
+}
+
+void FuzzerPassObfuscateConstants::
+    ObfuscateBoolConstantViaUnsignedIntConstantPair(
+        uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+        uint32_t unsigned_int_constant_id_1,
+        uint32_t unsigned_int_constant_id_2) {
+  auto unsigned_int_constant_1 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(unsigned_int_constant_id_1)
+          ->AsIntConstant();
+  auto unsigned_int_constant_2 =
+      GetIRContext()
+          ->get_constant_mgr()
+          ->FindDeclaredConstant(unsigned_int_constant_id_2)
+          ->AsIntConstant();
+  assert(unsigned_int_constant_1->words() != unsigned_int_constant_2->words() &&
+         "The constants should not be identical.");
+  bool first_constant_is_larger;
+  assert(unsigned_int_constant_1->type()->AsInteger()->width() ==
+             unsigned_int_constant_2->type()->AsInteger()->width() &&
+         "First and second floating-point constants must have the same width.");
+  assert(!unsigned_int_constant_1->type()->AsInteger()->IsSigned());
+  assert(!unsigned_int_constant_2->type()->AsInteger()->IsSigned());
+  if (unsigned_int_constant_1->type()->AsFloat()->width() == 32) {
+    first_constant_is_larger =
+        unsigned_int_constant_1->GetU32() > unsigned_int_constant_2->GetU32();
+  } else {
+    assert(unsigned_int_constant_1->type()->AsFloat()->width() == 64 &&
+           "Supported integer widths are 32 and 64.");
+    first_constant_is_larger =
+        unsigned_int_constant_1->GetU64() > unsigned_int_constant_2->GetU64();
+  }
+  std::vector<SpvOp> greater_than_opcodes{SpvOpUGreaterThan,
+                                          SpvOpUGreaterThanEqual};
+  std::vector<SpvOp> less_than_opcodes{SpvOpULessThan, SpvOpULessThanEqual};
+
+  ObfuscateBoolConstantViaConstantPair(
+      depth, bool_constant_use, greater_than_opcodes, less_than_opcodes,
+      unsigned_int_constant_id_1, unsigned_int_constant_id_2,
+      first_constant_is_larger);
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateBoolConstant(
+    uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
+  // We want to replace the boolean constant use with a binary expression over
+  // scalar constants, but only if we can then potentially replace the constants
+  // with uniforms of the same value.
+
+  auto available_types_with_uniforms =
+      GetFactManager()->GetTypesForWhichUniformValuesAreKnown();
+  if (available_types_with_uniforms.empty()) {
+    // Do not try to obfuscate if we do not have access to any uniform
+    // elements with known values.
+    return;
+  }
+  auto chosen_type_id = available_types_with_uniforms
+      [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+          static_cast<uint32_t>(available_types_with_uniforms.size()))];
+  auto available_constants =
+      GetFactManager()->GetConstantsAvailableFromUniformsForType(
+          GetIRContext(), chosen_type_id);
+  if (available_constants.size() == 1) {
+    // TODO(afd): for now we only obfuscate a boolean if there are at least
+    //  two constants available from uniforms, so that we can do a
+    //  comparison between them. It would be good to be able to do the
+    //  obfuscation even if there is only one such constant, if there is
+    //  also another regular constant available.
+    return;
+  }
+
+  // We know we have at least two known-to-be-constant uniforms of the chosen
+  // type.  Pick one of them at random.
+  auto constant_index_1 =
+      GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+          static_cast<uint32_t>(available_constants.size()));
+  uint32_t constant_index_2;
+
+  // Now choose another one distinct from the first one.
+  do {
+    constant_index_2 = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+        static_cast<uint32_t>(available_constants.size()));
+  } while (constant_index_1 == constant_index_2);
+
+  auto constant_id_1 = available_constants[constant_index_1];
+  auto constant_id_2 = available_constants[constant_index_2];
+
+  assert(constant_id_1 != 0 && constant_id_2 != 0 &&
+         "We should not find an available constant with an id of 0.");
+
+  // Now perform the obfuscation, according to whether the type of the constants
+  // is float, signed int, or unsigned int.
+  auto chosen_type = GetIRContext()->get_type_mgr()->GetType(chosen_type_id);
+  if (chosen_type->AsFloat()) {
+    ObfuscateBoolConstantViaFloatConstantPair(depth, constant_use,
+                                              constant_id_1, constant_id_2);
+  } else {
+    assert(chosen_type->AsInteger() &&
+           "We should only have uniform facts about ints and floats.");
+    if (chosen_type->AsInteger()->IsSigned()) {
+      ObfuscateBoolConstantViaSignedIntConstantPair(
+          depth, constant_use, constant_id_1, constant_id_2);
+    } else {
+      ObfuscateBoolConstantViaUnsignedIntConstantPair(
+          depth, constant_use, constant_id_1, constant_id_2);
+    }
+  }
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateScalarConstant(
+    uint32_t /*depth*/, const protobufs::IdUseDescriptor& constant_use) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2670): consider
+  //  additional ways to obfuscate scalar constants.
+
+  // Check whether we know that any uniforms are guaranteed to be equal to the
+  // scalar constant associated with |constant_use|.
+  auto uniform_descriptors = GetFactManager()->GetUniformDescriptorsForConstant(
+      GetIRContext(), constant_use.id_of_interest());
+  if (uniform_descriptors.empty()) {
+    // No relevant uniforms, so do not obfuscate.
+    return;
+  }
+
+  // Choose a random available uniform known to be equal to the constant.
+  protobufs::UniformBufferElementDescriptor uniform_descriptor =
+      uniform_descriptors
+          [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+              static_cast<uint32_t>(uniform_descriptors.size()))];
+  // Create, apply and record a transformation to replace the constant use with
+  // the result of a load from the chosen uniform.
+  auto transformation =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          constant_use, uniform_descriptor, GetFuzzerContext()->GetFreshId(),
+          GetFuzzerContext()->GetFreshId());
+  // Transformation should be applicable by construction.
+  assert(transformation::IsApplicable(transformation, GetIRContext(),
+                                      *GetFactManager()));
+  transformation::Apply(transformation, GetIRContext(), GetFactManager());
+  *GetTransformations()
+       ->add_transformation()
+       ->mutable_replace_constant_with_uniform() = transformation;
+}
+
+void FuzzerPassObfuscateConstants::ObfuscateConstant(
+    uint32_t depth, const protobufs::IdUseDescriptor& constant_use) {
+  switch (GetIRContext()
+              ->get_def_use_mgr()
+              ->GetDef(constant_use.id_of_interest())
+              ->opcode()) {
+    case SpvOpConstantTrue:
+    case SpvOpConstantFalse:
+      ObfuscateBoolConstant(depth, constant_use);
+      break;
+    case SpvOpConstant:
+      ObfuscateScalarConstant(depth, constant_use);
+      break;
+    default:
+      assert(false && "The opcode should be one of the above.");
+      break;
+  }
+}
+
+void FuzzerPassObfuscateConstants::MaybeAddConstantIdUse(
+    const opt::Instruction& inst, uint32_t in_operand_index,
+    uint32_t base_instruction_result_id,
+    const std::map<SpvOp, uint32_t>& skipped_opcode_count,
+    std::vector<protobufs::IdUseDescriptor>* constant_uses) {
+  if (inst.GetInOperand(in_operand_index).type != SPV_OPERAND_TYPE_ID) {
+    // The operand is not an id, so it cannot be a constant id.
+    return;
+  }
+  auto operand_id = inst.GetSingleWordInOperand(in_operand_index);
+  auto operand_definition =
+      GetIRContext()->get_def_use_mgr()->GetDef(operand_id);
+  switch (operand_definition->opcode()) {
+    case SpvOpConstantFalse:
+    case SpvOpConstantTrue:
+    case SpvOpConstant: {
+      // The operand is a constant id, so make an id use descriptor and record
+      // it.
+      protobufs::IdUseDescriptor id_use_descriptor;
+      id_use_descriptor.set_id_of_interest(operand_id);
+      id_use_descriptor.set_target_instruction_opcode(inst.opcode());
+      id_use_descriptor.set_in_operand_index(in_operand_index);
+      id_use_descriptor.set_base_instruction_result_id(
+          base_instruction_result_id);
+      id_use_descriptor.set_num_opcodes_to_ignore(
+          skipped_opcode_count.find(inst.opcode()) == skipped_opcode_count.end()
+              ? 0
+              : skipped_opcode_count.at(inst.opcode()));
+      constant_uses->push_back(id_use_descriptor);
+    } break;
+    default:
+      break;
+  }
+}
+
+void FuzzerPassObfuscateConstants::Apply() {
+  // First, gather up all the constant uses available in the module, by going
+  // through each block in each function.
+  std::vector<protobufs::IdUseDescriptor> constant_uses;
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // For each constant use we encounter we are going to make an id use
+      // descriptor. An id use is described with respect to a base instruction;
+      // if there are instructions at the start of the block without result ids,
+      // the base instruction will have to be the block's label.
+      uint32_t base_instruction_result_id = block.id();
+
+      // An id use descriptor also records how many instructions of a particular
+      // opcode need to be skipped in order to find the instruction of interest
+      // from the base instruction. We maintain a mapping that records a skip
+      // count for each relevant opcode.
+      std::map<SpvOp, uint32_t> skipped_opcode_count;
+
+      // Go through each instruction in the block.
+      for (auto& inst : block) {
+        if (inst.HasResultId()) {
+          // The instruction has a result id, so can be used as the base
+          // instruction from now on, until another instruction with a result id
+          // is encountered.
+          base_instruction_result_id = inst.result_id();
+          // Opcode skip counts were with respect to the previous base
+          // instruction and are now irrelevant.
+          skipped_opcode_count.clear();
+        }
+
+        // Consider each operand of the instruction, and add a constant id use
+        // for the operand if relevant.
+        for (uint32_t in_operand_index = 0;
+             in_operand_index < inst.NumInOperands(); in_operand_index++) {
+          MaybeAddConstantIdUse(inst, in_operand_index,
+                                base_instruction_result_id,
+                                skipped_opcode_count, &constant_uses);
+        }
+
+        if (!inst.HasResultId()) {
+          // The instruction has no result id, so in order to identify future id
+          // uses for instructions with this opcode from the existing base
+          // instruction, we need to increase the skip count for this opcode.
+          skipped_opcode_count[inst.opcode()] =
+              skipped_opcode_count.find(inst.opcode()) ==
+                      skipped_opcode_count.end()
+                  ? 1
+                  : skipped_opcode_count[inst.opcode()] + 1;
+        }
+      }
+    }
+  }
+
+  // Go through the constant uses in a random order by repeatedly pulling out a
+  // constant use at a random index.
+  while (!constant_uses.empty()) {
+    auto index = GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+        static_cast<uint32_t>(constant_uses.size()));
+    auto constant_use = std::move(constant_uses[index]);
+    constant_uses.erase(constant_uses.begin() + index);
+    // Decide probabilistically whether to skip or obfuscate this constant use.
+    if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+        GetFuzzerContext()->GetChanceOfObfuscatingConstant()) {
+      continue;
+    }
+    ObfuscateConstant(0, constant_use);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.h b/source/fuzz/fuzzer_pass_obfuscate_constants.h
new file mode 100644
index 0000000..03477a5
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
+#define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for turning uses of constants into more complex forms.
+// Examples include replacing 'true' with '42 < 52', and replacing '42' with
+// 'a.b.c' if 'a.b.c' is known to hold the value '42'.
+class FuzzerPassObfuscateConstants : public FuzzerPass {
+ public:
+  FuzzerPassObfuscateConstants(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassObfuscateConstants() override;
+
+  void Apply() override;
+
+ private:
+  // Applies 0 or more transformations to potentially obfuscate the constant
+  // use represented by |constant_use|.  The |depth| parameter controls how
+  // deeply obfuscation can recurse.
+  void ObfuscateConstant(uint32_t depth,
+                         const protobufs::IdUseDescriptor& constant_use);
+
+  // This method will try to turn |constant_use|, required to be a use of a
+  // boolean constant, into a binary expression on scalar constants, which may
+  // themselves be recursively obfuscated.
+  void ObfuscateBoolConstant(uint32_t depth,
+                             const protobufs::IdUseDescriptor& constant_use);
+
+  // This method will try to turn |constant_use|, required to be a use of a
+  // scalar constant, into the value loaded from a uniform known to have the
+  // same value as the constant (if one exists).
+  void ObfuscateScalarConstant(uint32_t depth,
+                               const protobufs::IdUseDescriptor& constant_use);
+
+  // Applies a transformation to replace the boolean constant usage represented
+  // by |bool_constant_use| with a binary expression involving
+  // |float_constant_id_1| and |float_constant_id_2|, which must not be equal
+  // to one another.  Possibly further obfuscates the uses of these float
+  // constants.  The |depth| parameter controls how deeply obfuscation can
+  // recurse.
+  void ObfuscateBoolConstantViaFloatConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      uint32_t float_constant_id_1, uint32_t float_constant_id_2);
+
+  // Similar to the above, but for signed int constants.
+  void ObfuscateBoolConstantViaSignedIntConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      uint32_t signed_int_constant_id_1, uint32_t signed_int_constant_id_2);
+
+  // Similar to the above, but for unsigned int constants.
+  void ObfuscateBoolConstantViaUnsignedIntConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      uint32_t unsigned_int_constant_id_1, uint32_t unsigned_int_constant_id_2);
+
+  // A helper method to capture the common parts of the above methods.
+  // The method is used to obfuscate the boolean constant usage represented by
+  // |bool_constant_use| by replacing it with '|constant_id_1| OP
+  // |constant_id_2|', where 'OP' is chosen from either |greater_than_opcodes|
+  // or |less_than_opcodes|.
+  //
+  // The two constant ids must not represent the same value, and thus
+  // |greater_than_opcodes| may include 'greater than or equal' opcodes
+  // (similar for |less_than_opcodes|).
+  void ObfuscateBoolConstantViaConstantPair(
+      uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
+      const std::vector<SpvOp>& greater_than_opcodes,
+      const std::vector<SpvOp>& less_than_opcodes, uint32_t constant_id_1,
+      uint32_t constant_id_2, bool first_constant_is_larger);
+
+  // A helper method to determine whether input operand |in_operand_index| of
+  // |inst| is the id of a constant, and add an id use descriptor to
+  // |candidate_constant_uses| if so.  The other parameters are used for id use
+  // descriptor construction.
+  void MaybeAddConstantIdUse(
+      const opt::Instruction& inst, uint32_t in_operand_index,
+      uint32_t base_instruction_result_id,
+      const std::map<SpvOp, uint32_t>& skipped_opcode_count,
+      std::vector<protobufs::IdUseDescriptor>* constant_uses);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 4bf8252..941a2ca 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -133,6 +133,8 @@
     TransformationAddTypeInt add_type_int = 7;
     TransformationAddDeadBreak add_dead_break = 8;
     TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9;
+    TransformationAddTypePointer add_type_pointer = 10;
+    TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
     // Add additional option using the next available number.
   }
 }
@@ -222,6 +224,22 @@
 
 }
 
+message TransformationAddTypePointer {
+
+  // Adds OpTypePointer to the module, with the given storage class and base
+  // type
+
+  // Id to be used for the type
+  uint32 fresh_id = 1;
+
+  // Pointer storage class
+  uint32 storage_class = 2;
+
+  // Id of the base type for the pointer
+  uint32 base_type_id = 3;
+
+}
+
 message TransformationMoveBlockDown {
 
   // A transformation that moves a basic block to be one position lower in
@@ -229,6 +247,24 @@
 
   // The id of the block to move down.
   uint32 block_id = 1;
+}
+
+message TransformationReplaceConstantWithUniform {
+
+  // Replaces a use of a constant id with the the result of a load from an
+  // element of uniform buffer known to hold the same value as the constant
+
+  // A descriptor for the id we would like to replace
+  IdUseDescriptor id_use_descriptor = 1;
+
+  // Uniform descriptor to identify which uniform value to choose
+  UniformBufferElementDescriptor uniform_descriptor = 2;
+
+  // Id that will store the result of an access chain
+  uint32 fresh_id_for_access_chain = 3;
+
+  // Id that will store the result of a load
+  uint32 fresh_id_for_load = 4;
 
 }
 
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index ccea91f..9c99675 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -24,8 +24,10 @@
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
 #include "source/fuzz/transformation_add_type_int.h"
+#include "source/fuzz/transformation_add_type_pointer.h"
 #include "source/fuzz/transformation_move_block_down.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_split_block.h"
 #include "source/opt/build_module.h"
 #include "source/util/make_unique.h"
@@ -58,6 +60,9 @@
     case protobufs::Transformation::TransformationCase::kAddTypeInt:
       return transformation::IsApplicable(transformation.add_type_int(),
                                           context, fact_manager);
+    case protobufs::Transformation::TransformationCase::kAddTypePointer:
+      return transformation::IsApplicable(transformation.add_type_pointer(),
+                                          context, fact_manager);
     case protobufs::Transformation::TransformationCase::kMoveBlockDown:
       return transformation::IsApplicable(transformation.move_block_down(),
                                           context, fact_manager);
@@ -66,6 +71,11 @@
       return transformation::IsApplicable(
           transformation.replace_boolean_constant_with_constant_binary(),
           context, fact_manager);
+    case protobufs::Transformation::TransformationCase::
+        kReplaceConstantWithUniform:
+      return transformation::IsApplicable(
+          transformation.replace_constant_with_uniform(), context,
+          fact_manager);
     case protobufs::Transformation::TransformationCase::kSplitBlock:
       return transformation::IsApplicable(transformation.split_block(), context,
                                           fact_manager);
@@ -107,6 +117,10 @@
       transformation::Apply(transformation.add_type_int(), context,
                             fact_manager);
       break;
+    case protobufs::Transformation::TransformationCase::kAddTypePointer:
+      transformation::Apply(transformation.add_type_pointer(), context,
+                            fact_manager);
+      break;
     case protobufs::Transformation::TransformationCase::kMoveBlockDown:
       transformation::Apply(transformation.move_block_down(), context,
                             fact_manager);
@@ -117,6 +131,11 @@
           transformation.replace_boolean_constant_with_constant_binary(),
           context, fact_manager);
       break;
+    case protobufs::Transformation::TransformationCase::
+        kReplaceConstantWithUniform:
+      transformation::Apply(transformation.replace_constant_with_uniform(),
+                            context, fact_manager);
+      break;
     case protobufs::Transformation::TransformationCase::kSplitBlock:
       transformation::Apply(transformation.split_block(), context,
                             fact_manager);
diff --git a/source/fuzz/transformation_add_type_pointer.cpp b/source/fuzz/transformation_add_type_pointer.cpp
new file mode 100644
index 0000000..9098cba
--- /dev/null
+++ b/source/fuzz/transformation_add_type_pointer.cpp
@@ -0,0 +1,61 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_pointer.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+using opt::IRContext;
+
+bool IsApplicable(const protobufs::TransformationAddTypePointer& message,
+                  IRContext* context,
+                  const spvtools::fuzz::FactManager& /*unused*/) {
+  // The id must be fresh.
+  if (!fuzzerutil::IsFreshId(context, message.fresh_id())) {
+    return false;
+  }
+  // The base type must be known.
+  return context->get_type_mgr()->GetType(message.base_type_id()) != nullptr;
+}
+
+void Apply(const protobufs::TransformationAddTypePointer& message,
+           IRContext* context, spvtools::fuzz::FactManager* /*unused*/) {
+  // Add the pointer type.
+  opt::Instruction::OperandList in_operands = {
+      {SPV_OPERAND_TYPE_STORAGE_CLASS, {message.storage_class()}},
+      {SPV_OPERAND_TYPE_ID, {message.base_type_id()}}};
+  context->module()->AddType(MakeUnique<opt::Instruction>(
+      context, SpvOpTypePointer, 0, message.fresh_id(), in_operands));
+  fuzzerutil::UpdateModuleIdBound(context, message.fresh_id());
+  // We have added an instruction to the module, so need to be careful about the
+  // validity of existing analyses.
+  context->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::TransformationAddTypePointer MakeTransformationAddTypePointer(
+    uint32_t fresh_id, SpvStorageClass storage_class, uint32_t base_type_id) {
+  protobufs::TransformationAddTypePointer result;
+  result.set_fresh_id(fresh_id);
+  result.set_storage_class(storage_class);
+  result.set_base_type_id(base_type_id);
+  return result;
+}
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_pointer.h b/source/fuzz/transformation_add_type_pointer.h
new file mode 100644
index 0000000..2cc4d30
--- /dev/null
+++ b/source/fuzz/transformation_add_type_pointer.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+// - |message.fresh_id| must not be used by the module
+// - |message.base_type_id| must be the result id of an OpType[...] instruction
+bool IsApplicable(const protobufs::TransformationAddTypePointer& message,
+                  opt::IRContext* context, const FactManager& fact_manager);
+
+// Adds an OpTypePointer instruction with the given storage class and base type
+// to the module.
+void Apply(const protobufs::TransformationAddTypePointer& message,
+           opt::IRContext* context, FactManager* fact_manager);
+
+// Helper factory to create a transformation message.
+protobufs::TransformationAddTypePointer MakeTransformationAddTypePointer(
+    uint32_t fresh_id, SpvStorageClass storage_class, uint32_t base_type_id);
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_TYPE_POINTER_H_
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp
new file mode 100644
index 0000000..fce458c
--- /dev/null
+++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp
@@ -0,0 +1,227 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+namespace {
+
+std::unique_ptr<opt::Instruction> MakeAccessChainInstruction(
+    const protobufs::TransformationReplaceConstantWithUniform& message,
+    spvtools::opt::IRContext* context, uint32_t constant_type_id) {
+  // The input operands for the access chain.
+  opt::Instruction::OperandList operands_for_access_chain;
+
+  // The first input operand is the id of the uniform variable.
+  operands_for_access_chain.push_back(
+      {SPV_OPERAND_TYPE_ID,
+       {message.uniform_descriptor().uniform_variable_id()}});
+
+  // The other input operands are the ids of the constants used to index into
+  // the uniform. The uniform buffer descriptor specifies a series of literals;
+  // for each we find the id of the instruction that defines it, and add these
+  // instruction ids as operands.
+  opt::analysis::Integer int_type(32, true);
+  auto registered_int_type =
+      context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
+  auto int_type_id = context->get_type_mgr()->GetId(&int_type);
+  for (auto index : message.uniform_descriptor().index()) {
+    opt::analysis::IntConstant int_constant(registered_int_type, {index});
+    auto constant_id = context->get_constant_mgr()->FindDeclaredConstant(
+        &int_constant, int_type_id);
+    operands_for_access_chain.push_back({SPV_OPERAND_TYPE_ID, {constant_id}});
+  }
+
+  // The type id for the access chain is a uniform pointer with base type
+  // matching the given constant id type.
+  auto type_and_pointer_type = context->get_type_mgr()->GetTypeAndPointerType(
+      constant_type_id, SpvStorageClassUniform);
+  assert(type_and_pointer_type.first != nullptr);
+  assert(type_and_pointer_type.second != nullptr);
+  auto pointer_to_uniform_constant_type_id =
+      context->get_type_mgr()->GetId(type_and_pointer_type.second.get());
+
+  return MakeUnique<opt::Instruction>(
+      context, SpvOpAccessChain, pointer_to_uniform_constant_type_id,
+      message.fresh_id_for_access_chain(), operands_for_access_chain);
+}
+
+std::unique_ptr<opt::Instruction> MakeLoadInstruction(
+    const protobufs::TransformationReplaceConstantWithUniform& message,
+    spvtools::opt::IRContext* context, uint32_t constant_type_id) {
+  opt::Instruction::OperandList operands_for_load = {
+      {SPV_OPERAND_TYPE_ID, {message.fresh_id_for_access_chain()}}};
+  return MakeUnique<opt::Instruction>(context, SpvOpLoad, constant_type_id,
+                                      message.fresh_id_for_load(),
+                                      operands_for_load);
+}
+
+}  // namespace
+
+bool IsApplicable(
+    const protobufs::TransformationReplaceConstantWithUniform& message,
+    spvtools::opt::IRContext* context,
+    const spvtools::fuzz::FactManager& fact_manager) {
+  // The following is really an invariant of the transformation rather than
+  // merely a requirement of the precondition.  We check it here since we cannot
+  // check it in the message constructor.
+  assert(message.fresh_id_for_access_chain() != message.fresh_id_for_load() &&
+         "Fresh ids for access chain and load result cannot be the same.");
+
+  // The ids for the access chain and load instructions must both be fresh.
+  if (!fuzzerutil::IsFreshId(context, message.fresh_id_for_access_chain())) {
+    return false;
+  }
+  if (!fuzzerutil::IsFreshId(context, message.fresh_id_for_load())) {
+    return false;
+  }
+
+  // The id specified in the id use descriptor must be that of a declared scalar
+  // constant.
+  auto declared_constant = context->get_constant_mgr()->FindDeclaredConstant(
+      message.id_use_descriptor().id_of_interest());
+  if (!declared_constant) {
+    return false;
+  }
+  if (!declared_constant->AsScalarConstant()) {
+    return false;
+  }
+
+  // The fact manager needs to believe that the uniform data element described
+  // by the uniform buffer element descriptor will hold a scalar value.
+  auto constant_id_associated_with_uniform =
+      fact_manager.GetConstantFromUniformDescriptor(
+          context, message.uniform_descriptor());
+  if (!constant_id_associated_with_uniform) {
+    return false;
+  }
+  auto constant_associated_with_uniform =
+      context->get_constant_mgr()->FindDeclaredConstant(
+          constant_id_associated_with_uniform);
+  assert(constant_associated_with_uniform &&
+         "The constant should be present in the module.");
+  if (!constant_associated_with_uniform->AsScalarConstant()) {
+    return false;
+  }
+
+  // The types and values of the scalar value held in the id specified by the id
+  // use descriptor and in the uniform data element specified by the uniform
+  // buffer element descriptor need to match on both type and value.
+  if (!declared_constant->type()->IsSame(
+          constant_associated_with_uniform->type())) {
+    return false;
+  }
+  if (declared_constant->AsScalarConstant()->words() !=
+      constant_associated_with_uniform->AsScalarConstant()->words()) {
+    return false;
+  }
+
+  // The id use descriptor must identify some instruction with respect to the
+  // module.
+  auto instruction_using_constant =
+      transformation::FindInstruction(message.id_use_descriptor(), context);
+  if (!instruction_using_constant) {
+    return false;
+  }
+
+  // The module needs to have a uniform pointer type suitable for indexing into
+  // the uniform variable, i.e. matching the type of the constant we wish to
+  // replace with a uniform.
+  opt::analysis::Pointer pointer_to_type_of_constant(declared_constant->type(),
+                                                     SpvStorageClassUniform);
+  if (!context->get_type_mgr()->GetId(&pointer_to_type_of_constant)) {
+    return false;
+  }
+
+  // In order to index into the uniform, the module has got to contain the int32
+  // type, plus an OpConstant for each of the indices of interest.
+  opt::analysis::Integer int_type(32, true);
+  if (!context->get_type_mgr()->GetId(&int_type)) {
+    return false;
+  }
+  auto registered_int_type =
+      context->get_type_mgr()->GetRegisteredType(&int_type)->AsInteger();
+  auto int_type_id = context->get_type_mgr()->GetId(&int_type);
+  for (auto index : message.uniform_descriptor().index()) {
+    opt::analysis::IntConstant int_constant(registered_int_type, {index});
+    if (!context->get_constant_mgr()->FindDeclaredConstant(&int_constant,
+                                                           int_type_id)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void Apply(const protobufs::TransformationReplaceConstantWithUniform& message,
+           spvtools::opt::IRContext* context,
+           spvtools::fuzz::FactManager* /*unused*/) {
+  // Get the instruction that contains the id use we wish to replace.
+  auto instruction_containing_constant_use =
+      transformation::FindInstruction(message.id_use_descriptor(), context);
+  assert(instruction_containing_constant_use &&
+         "Precondition requires that the id use can be found.");
+  assert(instruction_containing_constant_use->GetSingleWordInOperand(
+             message.id_use_descriptor().in_operand_index()) ==
+             message.id_use_descriptor().id_of_interest() &&
+         "Does not appear to be a usage of the desired id.");
+
+  // The id of the type for the constant whose use we wish to replace.
+  auto constant_type_id =
+      context->get_def_use_mgr()
+          ->GetDef(message.id_use_descriptor().id_of_interest())
+          ->type_id();
+
+  // Add an access chain instruction to target the uniform element.
+  instruction_containing_constant_use->InsertBefore(
+      MakeAccessChainInstruction(message, context, constant_type_id));
+
+  // Add a load from this access chain.
+  instruction_containing_constant_use->InsertBefore(
+      MakeLoadInstruction(message, context, constant_type_id));
+
+  // Adjust the instruction containing the usage of the constant so that this
+  // usage refers instead to the result of the load.
+  instruction_containing_constant_use->SetInOperand(
+      message.id_use_descriptor().in_operand_index(),
+      {message.fresh_id_for_load()});
+
+  // Update the module id bound to reflect the new instructions.
+  fuzzerutil::UpdateModuleIdBound(context, message.fresh_id_for_load());
+  fuzzerutil::UpdateModuleIdBound(context, message.fresh_id_for_access_chain());
+
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::TransformationReplaceConstantWithUniform
+MakeTransformationReplaceConstantWithUniform(
+    protobufs::IdUseDescriptor id_use,
+    protobufs::UniformBufferElementDescriptor uniform_descriptor,
+    uint32_t fresh_id_for_access_chain, uint32_t fresh_id_for_load) {
+  protobufs::TransformationReplaceConstantWithUniform result;
+  *result.mutable_id_use_descriptor() = std::move(id_use);
+  *result.mutable_uniform_descriptor() = std::move(uniform_descriptor);
+  result.set_fresh_id_for_access_chain(fresh_id_for_access_chain);
+  result.set_fresh_id_for_load(fresh_id_for_load);
+  return result;
+}
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.h b/source/fuzz/transformation_replace_constant_with_uniform.h
new file mode 100644
index 0000000..0112e80
--- /dev/null
+++ b/source/fuzz/transformation_replace_constant_with_uniform.h
@@ -0,0 +1,73 @@
+#include <utility>
+
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+// - |fresh_id_for_access_chain| and |fresh_id_for_load| must be distinct fresh
+//   ids.
+// - |uniform_descriptor| specifies a result id and a list of integer literal
+//   indices.
+//   As an example, suppose |uniform_descriptor| is (18, [0, 1, 0])
+//   It is required that:
+//     - the result id (18 in our example) is the id of some uniform variable
+//     - the module contains an integer constant instruction corresponding to
+//       each of the literal indices; in our example there must thus be
+//       OpConstant instructions %A and %B say for each of 0 and 1
+//     - it is legitimate to index into the uniform variable using the
+//       sequence of indices; in our example this means indexing into %18 using
+//       the sequence %A %B %A
+//     - the module contains a uniform pointer type corresponding to the type
+//       of the uniform data element obtained by following these indices
+// - |id_use_descriptor| identifies the use of some id %C.  It is required that:
+//     - this use does indeed exist in the module
+//     - %C is an OpConstant
+//     - According to the fact manager, the uniform data element specified by
+//       |uniform_descriptor| holds a value with the same type and value as %C
+bool IsApplicable(
+    const protobufs::TransformationReplaceConstantWithUniform& message,
+    opt::IRContext* context, const FactManager& fact_manager);
+
+// - Introduces two new instructions:
+//   - An access chain targeting the uniform data element specified by
+//     |uniform_descriptor|, with result id |fresh_id_for_access_chain|
+//   - A load from this access chain, with id |fresh_id_for_load|
+// - Replaces the id use specified by |id_use_descriptor| with
+//   |fresh_id_for_load|
+void Apply(const protobufs::TransformationReplaceConstantWithUniform& message,
+           opt::IRContext* context, FactManager* fact_manager);
+
+// Helper factory to create a transformation message.
+protobufs::TransformationReplaceConstantWithUniform
+MakeTransformationReplaceConstantWithUniform(
+    protobufs::IdUseDescriptor id_use,
+    protobufs::UniformBufferElementDescriptor uniform_descriptor,
+    uint32_t fresh_id_for_access_chain, uint32_t fresh_id_for_load);
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 2e4bca5..454206c 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -20,14 +20,17 @@
           fuzzer_replayer_test.cpp
           fact_manager_test.cpp
           fuzz_test_util.cpp
+          fuzzer_pass_add_useful_constructs_test.cpp
           transformation_add_constant_boolean_test.cpp
           transformation_add_constant_scalar_test.cpp
           transformation_add_dead_break_test.cpp
           transformation_add_type_boolean_test.cpp
           transformation_add_type_float_test.cpp
           transformation_add_type_int_test.cpp
+          transformation_add_type_pointer_test.cpp
           transformation_move_block_down_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
+          transformation_replace_constant_with_uniform_test.cpp
           transformation_split_block_test.cpp)
 
   add_spvtools_unittest(TARGET fuzz
diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp
index 0209a60..bf07f35 100644
--- a/test/fuzz/fact_manager_test.cpp
+++ b/test/fuzz/fact_manager_test.cpp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <limits>
+
 #include "source/fuzz/fact_manager.h"
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
@@ -529,6 +531,101 @@
   }
 }
 
+TEST(FactManagerTest, NonFiniteFactsAreNotValid) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "f"
+               OpMemberName %7 1 "d"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 8
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %10 = OpTypeFloat 64
+          %7 = OpTypeStruct %6 %10
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  auto uniform_buffer_element_descriptor_f =
+      MakeUniformBufferElementDescriptor(9, {0});
+
+  auto uniform_buffer_element_descriptor_d =
+      MakeUniformBufferElementDescriptor(9, {1});
+
+  if (std::numeric_limits<float>::has_infinity) {
+    // f == +inf
+    float positive_infinity_float = std::numeric_limits<float>::infinity();
+    uint32_t words[1];
+    memcpy(words, &positive_infinity_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
+                               uniform_buffer_element_descriptor_f));
+    // f == -inf
+    float negative_infinity_float = std::numeric_limits<float>::infinity();
+    memcpy(words, &negative_infinity_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
+                               uniform_buffer_element_descriptor_f));
+  }
+
+  if (std::numeric_limits<float>::has_quiet_NaN) {
+    // f == NaN
+    float quiet_nan_float = std::numeric_limits<float>::quiet_NaN();
+    uint32_t words[1];
+    memcpy(words, &quiet_nan_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
+                               uniform_buffer_element_descriptor_f));
+  }
+
+  if (std::numeric_limits<double>::has_infinity) {
+    // d == +inf
+    double positive_infinity_double = std::numeric_limits<double>::infinity();
+    uint32_t words[2];
+    memcpy(words, &positive_infinity_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
+                               {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+    // d == -inf
+    double negative_infinity_double = -std::numeric_limits<double>::infinity();
+    memcpy(words, &negative_infinity_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
+                               {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+  }
+
+  if (std::numeric_limits<double>::has_quiet_NaN) {
+    // d == NaN
+    double quiet_nan_double = std::numeric_limits<double>::quiet_NaN();
+    uint32_t words[2];
+    memcpy(words, &quiet_nan_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
+                               {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+  }
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
new file mode 100644
index 0000000..3cea325
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
@@ -0,0 +1,393 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_useful_constructs.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+bool AddFactHelper(
+    FactManager* fact_manager, opt::IRContext* context, uint32_t word,
+    const protobufs::UniformBufferElementDescriptor& descriptor) {
+  protobufs::FactConstantUniform constant_uniform_fact;
+  constant_uniform_fact.add_constant_word(word);
+  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+      descriptor;
+  protobufs::Fact fact;
+  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+  return fact_manager->AddFact(fact, context);
+}
+
+TEST(FuzzerPassAddUsefulConstructsTest, CheckBasicStuffIsAdded) {
+  // The SPIR-V came from the following empty GLSL shader:
+  //
+  // #version 450
+  //
+  // void main()
+  // {
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassAddUsefulConstructs pass(context.get(), &fact_manager,
+                                     &fuzzer_context, &transformation_sequence);
+  pass.Apply();
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeBool
+        %101 = OpTypeInt 32 1
+        %102 = OpTypeInt 32 0
+        %103 = OpTypeFloat 32
+        %104 = OpConstantTrue %100
+        %105 = OpConstantFalse %100
+        %106 = OpConstant %101 0
+        %107 = OpConstant %101 1
+        %108 = OpConstant %102 0
+        %109 = OpConstant %102 1
+        %110 = OpConstant %103 0
+        %111 = OpConstant %103 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+TEST(FuzzerPassAddUsefulConstructsTest,
+     CheckTypesIndicesAndConstantsAddedForUniformFacts) {
+  // The SPIR-V came from the following GLSL shader:
+  //
+  // #version 450
+  //
+  // struct S {
+  //   int x;
+  //   float y;
+  //   int z;
+  //   int w;
+  // };
+  //
+  // uniform buf {
+  //   S s;
+  //   uint w[10];
+  // };
+  //
+  // void main() {
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "S"
+               OpMemberName %8 0 "x"
+               OpMemberName %8 1 "y"
+               OpMemberName %8 2 "z"
+               OpMemberName %8 3 "w"
+               OpName %12 "buf"
+               OpMemberName %12 0 "s"
+               OpMemberName %12 1 "w"
+               OpName %14 ""
+               OpMemberDecorate %8 0 Offset 0
+               OpMemberDecorate %8 1 Offset 4
+               OpMemberDecorate %8 2 Offset 8
+               OpMemberDecorate %8 3 Offset 12
+               OpDecorate %11 ArrayStride 16
+               OpMemberDecorate %12 0 Offset 0
+               OpMemberDecorate %12 1 Offset 16
+               OpDecorate %12 Block
+               OpDecorate %14 DescriptorSet 0
+               OpDecorate %14 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7 %6 %6
+          %9 = OpTypeInt 32 0
+         %10 = OpConstant %9 10
+         %11 = OpTypeArray %9 %10
+         %12 = OpTypeStruct %8 %11
+         %13 = OpTypePointer Uniform %12
+         %14 = OpVariable %13 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0).get(), 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  // Add some uniform facts.
+
+  // buf.s.x == 200
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 200,
+                            MakeUniformBufferElementDescriptor(14, {0, 0})));
+
+  // buf.s.y == 0.5
+  const float float_value = 0.5;
+  uint32_t float_value_as_uint;
+  memcpy(&float_value_as_uint, &float_value, sizeof(float_value));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_value_as_uint,
+                            MakeUniformBufferElementDescriptor(14, {0, 1})));
+
+  // buf.s.z == 300
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 300,
+                            MakeUniformBufferElementDescriptor(14, {0, 2})));
+
+  // buf.s.w == 400
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 400,
+                            MakeUniformBufferElementDescriptor(14, {0, 3})));
+
+  // buf.w[6] = 22
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 22,
+                            MakeUniformBufferElementDescriptor(14, {1, 6})));
+
+  // buf.w[8] = 23
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 23,
+                            MakeUniformBufferElementDescriptor(14, {1, 8})));
+
+  // Assert some things about the module that are not true prior to adding the
+  // pass
+
+  {
+    // No uniform int pointer
+    opt::analysis::Integer temp_type_signed_int(32, true);
+    opt::analysis::Integer* registered_type_signed_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_signed_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_signed_int(
+        registered_type_signed_int, SpvStorageClassUniform);
+    ASSERT_EQ(0,
+              context->get_type_mgr()->GetId(&type_pointer_uniform_signed_int));
+
+    // No uniform uint pointer
+    opt::analysis::Integer temp_type_unsigned_int(32, false);
+    opt::analysis::Integer* registered_type_unsigned_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_unsigned_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_unsigned_int(
+        registered_type_unsigned_int, SpvStorageClassUniform);
+    ASSERT_EQ(
+        0, context->get_type_mgr()->GetId(&type_pointer_uniform_unsigned_int));
+
+    // No uniform float pointer
+    opt::analysis::Float temp_type_float(32);
+    opt::analysis::Float* registered_type_float =
+        context->get_type_mgr()->GetRegisteredType(&temp_type_float)->AsFloat();
+    opt::analysis::Pointer type_pointer_uniform_float(registered_type_float,
+                                                      SpvStorageClassUniform);
+    ASSERT_EQ(0, context->get_type_mgr()->GetId(&type_pointer_uniform_float));
+
+    // No int constants 200, 300 nor 400
+    opt::analysis::IntConstant int_constant_200(registered_type_signed_int,
+                                                {200});
+    opt::analysis::IntConstant int_constant_300(registered_type_signed_int,
+                                                {300});
+    opt::analysis::IntConstant int_constant_400(registered_type_signed_int,
+                                                {400});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_200));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_300));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_400));
+
+    // No float constant 0.5
+    opt::analysis::FloatConstant float_constant_zero_point_five(
+        registered_type_float, {float_value_as_uint});
+    ASSERT_EQ(nullptr, context->get_constant_mgr()->FindConstant(
+                           &float_constant_zero_point_five));
+
+    // No uint constant 22
+    opt::analysis::IntConstant uint_constant_22(registered_type_unsigned_int,
+                                                {22});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_22));
+
+    // No uint constant 23
+    opt::analysis::IntConstant uint_constant_23(registered_type_unsigned_int,
+                                                {23});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_23));
+
+    // No int constants 0, 1, 2, 3, 6, 8
+    opt::analysis::IntConstant int_constant_0(registered_type_signed_int, {0});
+    opt::analysis::IntConstant int_constant_1(registered_type_signed_int, {1});
+    opt::analysis::IntConstant int_constant_2(registered_type_signed_int, {2});
+    opt::analysis::IntConstant int_constant_3(registered_type_signed_int, {3});
+    opt::analysis::IntConstant int_constant_6(registered_type_signed_int, {6});
+    opt::analysis::IntConstant int_constant_8(registered_type_signed_int, {8});
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_0));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_1));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_2));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_3));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_6));
+    ASSERT_EQ(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_8));
+  }
+
+  FuzzerPassAddUsefulConstructs pass(context.get(), &fact_manager,
+                                     &fuzzer_context, &transformation_sequence);
+  pass.Apply();
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Now assert some things about the module that should be true following the
+  // pass.
+
+  // We reconstruct all necessary types and constants to guard against the type
+  // and constant managers for the module having been invalidated.
+
+  {
+    // Uniform int pointer now present
+    opt::analysis::Integer temp_type_signed_int(32, true);
+    opt::analysis::Integer* registered_type_signed_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_signed_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_signed_int(
+        registered_type_signed_int, SpvStorageClassUniform);
+    ASSERT_NE(0,
+              context->get_type_mgr()->GetId(&type_pointer_uniform_signed_int));
+
+    // Uniform uint pointer now present
+    opt::analysis::Integer temp_type_unsigned_int(32, false);
+    opt::analysis::Integer* registered_type_unsigned_int =
+        context->get_type_mgr()
+            ->GetRegisteredType(&temp_type_unsigned_int)
+            ->AsInteger();
+    opt::analysis::Pointer type_pointer_uniform_unsigned_int(
+        registered_type_unsigned_int, SpvStorageClassUniform);
+    ASSERT_NE(
+        0, context->get_type_mgr()->GetId(&type_pointer_uniform_unsigned_int));
+
+    // Uniform float pointer now present
+    opt::analysis::Float temp_type_float(32);
+    opt::analysis::Float* registered_type_float =
+        context->get_type_mgr()->GetRegisteredType(&temp_type_float)->AsFloat();
+    opt::analysis::Pointer type_pointer_uniform_float(registered_type_float,
+                                                      SpvStorageClassUniform);
+    ASSERT_NE(0, context->get_type_mgr()->GetId(&type_pointer_uniform_float));
+
+    // int constants 200, 300, 400 now present
+    opt::analysis::IntConstant int_constant_200(registered_type_signed_int,
+                                                {200});
+    opt::analysis::IntConstant int_constant_300(registered_type_signed_int,
+                                                {300});
+    opt::analysis::IntConstant int_constant_400(registered_type_signed_int,
+                                                {400});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_200));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_300));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_400));
+
+    // float constant 0.5 now present
+    opt::analysis::FloatConstant float_constant_zero_point_five(
+        registered_type_float, {float_value_as_uint});
+    ASSERT_NE(nullptr, context->get_constant_mgr()->FindConstant(
+                           &float_constant_zero_point_five));
+
+    // uint constant 22 now present
+    opt::analysis::IntConstant uint_constant_22(registered_type_unsigned_int,
+                                                {22});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_22));
+
+    // uint constant 23 now present
+    opt::analysis::IntConstant uint_constant_23(registered_type_unsigned_int,
+                                                {23});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&uint_constant_23));
+
+    // int constants 0, 1, 2, 3, 6, 8 now present
+    opt::analysis::IntConstant int_constant_0(registered_type_signed_int, {0});
+    opt::analysis::IntConstant int_constant_1(registered_type_signed_int, {1});
+    opt::analysis::IntConstant int_constant_2(registered_type_signed_int, {2});
+    opt::analysis::IntConstant int_constant_3(registered_type_signed_int, {3});
+    opt::analysis::IntConstant int_constant_6(registered_type_signed_int, {6});
+    opt::analysis::IntConstant int_constant_8(registered_type_signed_int, {8});
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_0));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_1));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_2));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_3));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_6));
+    ASSERT_NE(nullptr,
+              context->get_constant_mgr()->FindConstant(&int_constant_8));
+  }
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index 3adf766..bd753af 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -14,6 +14,7 @@
 
 #include "source/fuzz/fuzzer.h"
 #include "source/fuzz/replayer.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -25,8 +26,9 @@
 // the binary produced after each fuzzer run is valid, and that replaying
 // the transformations that were applied during fuzzing leads to an
 // identical binary.
-void RunFuzzerAndReplayer(const std::string& shader, uint32_t initial_seed,
-                          uint32_t num_runs) {
+void RunFuzzerAndReplayer(const std::string& shader,
+                          const protobufs::FactSequence& initial_facts,
+                          uint32_t initial_seed, uint32_t num_runs) {
   const auto env = SPV_ENV_UNIVERSAL_1_3;
 
   std::vector<uint32_t> binary_in;
@@ -35,23 +37,27 @@
   ASSERT_TRUE(t.Validate(binary_in));
 
   for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
-    protobufs::FactSequence initial_facts;
     std::vector<uint32_t> fuzzer_binary_out;
     protobufs::TransformationSequence fuzzer_transformation_sequence_out;
     spvtools::FuzzerOptions fuzzer_options;
     spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed);
 
     Fuzzer fuzzer(env);
-    fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
-               &fuzzer_transformation_sequence_out, fuzzer_options);
+    auto fuzzer_result_status =
+        fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out,
+                   &fuzzer_transformation_sequence_out, fuzzer_options);
+    ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
     ASSERT_TRUE(t.Validate(fuzzer_binary_out));
 
     std::vector<uint32_t> replayer_binary_out;
     protobufs::TransformationSequence replayer_transformation_sequence_out;
 
     Replayer replayer(env);
-    replayer.Run(binary_in, initial_facts, fuzzer_transformation_sequence_out,
-                 &replayer_binary_out, &replayer_transformation_sequence_out);
+    auto replayer_result_status = replayer.Run(
+        binary_in, initial_facts, fuzzer_transformation_sequence_out,
+        &replayer_binary_out, &replayer_transformation_sequence_out);
+    ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
+              replayer_result_status);
 
     // After replaying the transformations applied by the fuzzer, exactly those
     // transformations should have been applied, and the binary resulting from
@@ -233,7 +239,7 @@
 
   // Do 10 fuzzer runs, starting from an initial seed of 0 (seed value chosen
   // arbitrarily).
-  RunFuzzerAndReplayer(shader, 0, 10);
+  RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 10);
 }
 
 TEST(FuzzerReplayerTest, Miscellaneous2) {
@@ -478,7 +484,492 @@
 
   // Do 10 fuzzer runs, starting from an initial seed of 10 (seed value chosen
   // arbitrarily).
-  RunFuzzerAndReplayer(shader, 10, 10);
+  RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 10);
+}
+
+TEST(FuzzerReplayerTest, Miscellaneous3) {
+  // The SPIR-V came from this GLSL, which was then optimized using spirv-opt
+  // with the -O argument:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(location = 0) out vec4 _GLF_color;
+  //
+  // layout(set = 0, binding = 0) uniform buf0 {
+  //  vec2 resolution;
+  // };
+  // void main(void)
+  // {
+  //  float A[50];
+  //  for(
+  //      int i = 0;
+  //      i < 200;
+  //      i ++
+  //  )
+  //   {
+  //    if(i >= int(resolution.x))
+  //     {
+  //      break;
+  //     }
+  //    if((4 * (i / 4)) == i)
+  //     {
+  //      A[i / 4] = float(i);
+  //     }
+  //   }
+  //  for(
+  //      int i = 0;
+  //      i < 50;
+  //      i ++
+  //  )
+  //   {
+  //    if(i < int(gl_FragCoord.x))
+  //     {
+  //      break;
+  //     }
+  //    if(i > 0)
+  //     {
+  //      A[i] += A[i - 1];
+  //     }
+  //   }
+  //  if(int(gl_FragCoord.x) < 20)
+  //   {
+  //    _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0);
+  //   }
+  //  else
+  //   if(int(gl_FragCoord.x) < 40)
+  //    {
+  //     _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0);
+  //    }
+  //   else
+  //    if(int(gl_FragCoord.x) < 60)
+  //     {
+  //      _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y,
+  //      1.0, 1.0);
+  //     }
+  //    else
+  //     if(int(gl_FragCoord.x) < 80)
+  //      {
+  //       _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y,
+  //       1.0, 1.0);
+  //      }
+  //     else
+  //      if(int(gl_FragCoord.x) < 100)
+  //       {
+  //        _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y,
+  //        1.0, 1.0);
+  //       }
+  //      else
+  //       if(int(gl_FragCoord.x) < 120)
+  //        {
+  //         _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y,
+  //         1.0, 1.0);
+  //        }
+  //       else
+  //        if(int(gl_FragCoord.x) < 140)
+  //         {
+  //          _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y,
+  //          1.0, 1.0);
+  //         }
+  //        else
+  //         if(int(gl_FragCoord.x) < 160)
+  //          {
+  //           _GLF_color = vec4(A[35] / resolution.x, A[39] /
+  //           resolution.y, 1.0, 1.0);
+  //          }
+  //         else
+  //          if(int(gl_FragCoord.x) < 180)
+  //           {
+  //            _GLF_color = vec4(A[40] / resolution.x, A[44] /
+  //            resolution.y, 1.0, 1.0);
+  //           }
+  //          else
+  //           if(int(gl_FragCoord.x) < 180)
+  //            {
+  //             _GLF_color = vec4(A[45] / resolution.x, A[49] /
+  //             resolution.y, 1.0, 1.0);
+  //            }
+  //           else
+  //            {
+  //             discard;
+  //            }
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %68 %100
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %22 "buf0"
+               OpMemberName %22 0 "resolution"
+               OpName %24 ""
+               OpName %46 "A"
+               OpName %68 "gl_FragCoord"
+               OpName %100 "_GLF_color"
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %24 DescriptorSet 0
+               OpDecorate %24 Binding 0
+               OpDecorate %37 RelaxedPrecision
+               OpDecorate %38 RelaxedPrecision
+               OpDecorate %55 RelaxedPrecision
+               OpDecorate %68 BuiltIn FragCoord
+               OpDecorate %83 RelaxedPrecision
+               OpDecorate %91 RelaxedPrecision
+               OpDecorate %100 Location 0
+               OpDecorate %302 RelaxedPrecision
+               OpDecorate %304 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 200
+         %17 = OpTypeBool
+         %20 = OpTypeFloat 32
+         %21 = OpTypeVector %20 2
+         %22 = OpTypeStruct %21
+         %23 = OpTypePointer Uniform %22
+         %24 = OpVariable %23 Uniform
+         %25 = OpTypeInt 32 0
+         %26 = OpConstant %25 0
+         %27 = OpTypePointer Uniform %20
+         %35 = OpConstant %6 4
+         %43 = OpConstant %25 50
+         %44 = OpTypeArray %20 %43
+         %45 = OpTypePointer Function %44
+         %51 = OpTypePointer Function %20
+         %54 = OpConstant %6 1
+         %63 = OpConstant %6 50
+         %66 = OpTypeVector %20 4
+         %67 = OpTypePointer Input %66
+         %68 = OpVariable %67 Input
+         %69 = OpTypePointer Input %20
+         %95 = OpConstant %6 20
+         %99 = OpTypePointer Output %66
+        %100 = OpVariable %99 Output
+        %108 = OpConstant %25 1
+        %112 = OpConstant %20 1
+        %118 = OpConstant %6 40
+        %122 = OpConstant %6 5
+        %128 = OpConstant %6 9
+        %139 = OpConstant %6 60
+        %143 = OpConstant %6 10
+        %149 = OpConstant %6 14
+        %160 = OpConstant %6 80
+        %164 = OpConstant %6 15
+        %170 = OpConstant %6 19
+        %181 = OpConstant %6 100
+        %190 = OpConstant %6 24
+        %201 = OpConstant %6 120
+        %205 = OpConstant %6 25
+        %211 = OpConstant %6 29
+        %222 = OpConstant %6 140
+        %226 = OpConstant %6 30
+        %232 = OpConstant %6 34
+        %243 = OpConstant %6 160
+        %247 = OpConstant %6 35
+        %253 = OpConstant %6 39
+        %264 = OpConstant %6 180
+        %273 = OpConstant %6 44
+        %287 = OpConstant %6 45
+        %293 = OpConstant %6 49
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %46 = OpVariable %45 Function
+               OpBranch %10
+         %10 = OpLabel
+        %302 = OpPhi %6 %9 %5 %55 %42
+         %18 = OpSLessThan %17 %302 %16
+               OpLoopMerge %12 %42 None
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %28 = OpAccessChain %27 %24 %9 %26
+         %29 = OpLoad %20 %28
+         %30 = OpConvertFToS %6 %29
+         %31 = OpSGreaterThanEqual %17 %302 %30
+               OpSelectionMerge %33 None
+               OpBranchConditional %31 %32 %33
+         %32 = OpLabel
+               OpBranch %12
+         %33 = OpLabel
+         %37 = OpSDiv %6 %302 %35
+         %38 = OpIMul %6 %35 %37
+         %40 = OpIEqual %17 %38 %302
+               OpSelectionMerge %42 None
+               OpBranchConditional %40 %41 %42
+         %41 = OpLabel
+         %50 = OpConvertSToF %20 %302
+         %52 = OpAccessChain %51 %46 %37
+               OpStore %52 %50
+               OpBranch %42
+         %42 = OpLabel
+         %55 = OpIAdd %6 %302 %54
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %57
+         %57 = OpLabel
+        %304 = OpPhi %6 %9 %12 %91 %80
+         %64 = OpSLessThan %17 %304 %63
+               OpLoopMerge %59 %80 None
+               OpBranchConditional %64 %58 %59
+         %58 = OpLabel
+         %70 = OpAccessChain %69 %68 %26
+         %71 = OpLoad %20 %70
+         %72 = OpConvertFToS %6 %71
+         %73 = OpSLessThan %17 %304 %72
+               OpSelectionMerge %75 None
+               OpBranchConditional %73 %74 %75
+         %74 = OpLabel
+               OpBranch %59
+         %75 = OpLabel
+         %78 = OpSGreaterThan %17 %304 %9
+               OpSelectionMerge %80 None
+               OpBranchConditional %78 %79 %80
+         %79 = OpLabel
+         %83 = OpISub %6 %304 %54
+         %84 = OpAccessChain %51 %46 %83
+         %85 = OpLoad %20 %84
+         %86 = OpAccessChain %51 %46 %304
+         %87 = OpLoad %20 %86
+         %88 = OpFAdd %20 %87 %85
+               OpStore %86 %88
+               OpBranch %80
+         %80 = OpLabel
+         %91 = OpIAdd %6 %304 %54
+               OpBranch %57
+         %59 = OpLabel
+         %92 = OpAccessChain %69 %68 %26
+         %93 = OpLoad %20 %92
+         %94 = OpConvertFToS %6 %93
+         %96 = OpSLessThan %17 %94 %95
+               OpSelectionMerge %98 None
+               OpBranchConditional %96 %97 %114
+         %97 = OpLabel
+        %101 = OpAccessChain %51 %46 %9
+        %102 = OpLoad %20 %101
+        %103 = OpAccessChain %27 %24 %9 %26
+        %104 = OpLoad %20 %103
+        %105 = OpFDiv %20 %102 %104
+        %106 = OpAccessChain %51 %46 %35
+        %107 = OpLoad %20 %106
+        %109 = OpAccessChain %27 %24 %9 %108
+        %110 = OpLoad %20 %109
+        %111 = OpFDiv %20 %107 %110
+        %113 = OpCompositeConstruct %66 %105 %111 %112 %112
+               OpStore %100 %113
+               OpBranch %98
+        %114 = OpLabel
+        %119 = OpSLessThan %17 %94 %118
+               OpSelectionMerge %121 None
+               OpBranchConditional %119 %120 %135
+        %120 = OpLabel
+        %123 = OpAccessChain %51 %46 %122
+        %124 = OpLoad %20 %123
+        %125 = OpAccessChain %27 %24 %9 %26
+        %126 = OpLoad %20 %125
+        %127 = OpFDiv %20 %124 %126
+        %129 = OpAccessChain %51 %46 %128
+        %130 = OpLoad %20 %129
+        %131 = OpAccessChain %27 %24 %9 %108
+        %132 = OpLoad %20 %131
+        %133 = OpFDiv %20 %130 %132
+        %134 = OpCompositeConstruct %66 %127 %133 %112 %112
+               OpStore %100 %134
+               OpBranch %121
+        %135 = OpLabel
+        %140 = OpSLessThan %17 %94 %139
+               OpSelectionMerge %142 None
+               OpBranchConditional %140 %141 %156
+        %141 = OpLabel
+        %144 = OpAccessChain %51 %46 %143
+        %145 = OpLoad %20 %144
+        %146 = OpAccessChain %27 %24 %9 %26
+        %147 = OpLoad %20 %146
+        %148 = OpFDiv %20 %145 %147
+        %150 = OpAccessChain %51 %46 %149
+        %151 = OpLoad %20 %150
+        %152 = OpAccessChain %27 %24 %9 %108
+        %153 = OpLoad %20 %152
+        %154 = OpFDiv %20 %151 %153
+        %155 = OpCompositeConstruct %66 %148 %154 %112 %112
+               OpStore %100 %155
+               OpBranch %142
+        %156 = OpLabel
+        %161 = OpSLessThan %17 %94 %160
+               OpSelectionMerge %163 None
+               OpBranchConditional %161 %162 %177
+        %162 = OpLabel
+        %165 = OpAccessChain %51 %46 %164
+        %166 = OpLoad %20 %165
+        %167 = OpAccessChain %27 %24 %9 %26
+        %168 = OpLoad %20 %167
+        %169 = OpFDiv %20 %166 %168
+        %171 = OpAccessChain %51 %46 %170
+        %172 = OpLoad %20 %171
+        %173 = OpAccessChain %27 %24 %9 %108
+        %174 = OpLoad %20 %173
+        %175 = OpFDiv %20 %172 %174
+        %176 = OpCompositeConstruct %66 %169 %175 %112 %112
+               OpStore %100 %176
+               OpBranch %163
+        %177 = OpLabel
+        %182 = OpSLessThan %17 %94 %181
+               OpSelectionMerge %184 None
+               OpBranchConditional %182 %183 %197
+        %183 = OpLabel
+        %185 = OpAccessChain %51 %46 %95
+        %186 = OpLoad %20 %185
+        %187 = OpAccessChain %27 %24 %9 %26
+        %188 = OpLoad %20 %187
+        %189 = OpFDiv %20 %186 %188
+        %191 = OpAccessChain %51 %46 %190
+        %192 = OpLoad %20 %191
+        %193 = OpAccessChain %27 %24 %9 %108
+        %194 = OpLoad %20 %193
+        %195 = OpFDiv %20 %192 %194
+        %196 = OpCompositeConstruct %66 %189 %195 %112 %112
+               OpStore %100 %196
+               OpBranch %184
+        %197 = OpLabel
+        %202 = OpSLessThan %17 %94 %201
+               OpSelectionMerge %204 None
+               OpBranchConditional %202 %203 %218
+        %203 = OpLabel
+        %206 = OpAccessChain %51 %46 %205
+        %207 = OpLoad %20 %206
+        %208 = OpAccessChain %27 %24 %9 %26
+        %209 = OpLoad %20 %208
+        %210 = OpFDiv %20 %207 %209
+        %212 = OpAccessChain %51 %46 %211
+        %213 = OpLoad %20 %212
+        %214 = OpAccessChain %27 %24 %9 %108
+        %215 = OpLoad %20 %214
+        %216 = OpFDiv %20 %213 %215
+        %217 = OpCompositeConstruct %66 %210 %216 %112 %112
+               OpStore %100 %217
+               OpBranch %204
+        %218 = OpLabel
+        %223 = OpSLessThan %17 %94 %222
+               OpSelectionMerge %225 None
+               OpBranchConditional %223 %224 %239
+        %224 = OpLabel
+        %227 = OpAccessChain %51 %46 %226
+        %228 = OpLoad %20 %227
+        %229 = OpAccessChain %27 %24 %9 %26
+        %230 = OpLoad %20 %229
+        %231 = OpFDiv %20 %228 %230
+        %233 = OpAccessChain %51 %46 %232
+        %234 = OpLoad %20 %233
+        %235 = OpAccessChain %27 %24 %9 %108
+        %236 = OpLoad %20 %235
+        %237 = OpFDiv %20 %234 %236
+        %238 = OpCompositeConstruct %66 %231 %237 %112 %112
+               OpStore %100 %238
+               OpBranch %225
+        %239 = OpLabel
+        %244 = OpSLessThan %17 %94 %243
+               OpSelectionMerge %246 None
+               OpBranchConditional %244 %245 %260
+        %245 = OpLabel
+        %248 = OpAccessChain %51 %46 %247
+        %249 = OpLoad %20 %248
+        %250 = OpAccessChain %27 %24 %9 %26
+        %251 = OpLoad %20 %250
+        %252 = OpFDiv %20 %249 %251
+        %254 = OpAccessChain %51 %46 %253
+        %255 = OpLoad %20 %254
+        %256 = OpAccessChain %27 %24 %9 %108
+        %257 = OpLoad %20 %256
+        %258 = OpFDiv %20 %255 %257
+        %259 = OpCompositeConstruct %66 %252 %258 %112 %112
+               OpStore %100 %259
+               OpBranch %246
+        %260 = OpLabel
+        %265 = OpSLessThan %17 %94 %264
+               OpSelectionMerge %267 None
+               OpBranchConditional %265 %266 %280
+        %266 = OpLabel
+        %268 = OpAccessChain %51 %46 %118
+        %269 = OpLoad %20 %268
+        %270 = OpAccessChain %27 %24 %9 %26
+        %271 = OpLoad %20 %270
+        %272 = OpFDiv %20 %269 %271
+        %274 = OpAccessChain %51 %46 %273
+        %275 = OpLoad %20 %274
+        %276 = OpAccessChain %27 %24 %9 %108
+        %277 = OpLoad %20 %276
+        %278 = OpFDiv %20 %275 %277
+        %279 = OpCompositeConstruct %66 %272 %278 %112 %112
+               OpStore %100 %279
+               OpBranch %267
+        %280 = OpLabel
+               OpSelectionMerge %285 None
+               OpBranchConditional %265 %285 %300
+        %285 = OpLabel
+        %288 = OpAccessChain %51 %46 %287
+        %289 = OpLoad %20 %288
+        %290 = OpAccessChain %27 %24 %9 %26
+        %291 = OpLoad %20 %290
+        %292 = OpFDiv %20 %289 %291
+        %294 = OpAccessChain %51 %46 %293
+        %295 = OpLoad %20 %294
+        %296 = OpAccessChain %27 %24 %9 %108
+        %297 = OpLoad %20 %296
+        %298 = OpFDiv %20 %295 %297
+        %299 = OpCompositeConstruct %66 %292 %298 %112 %112
+               OpStore %100 %299
+               OpBranch %267
+        %300 = OpLabel
+               OpKill
+        %267 = OpLabel
+               OpBranch %246
+        %246 = OpLabel
+               OpBranch %225
+        %225 = OpLabel
+               OpBranch %204
+        %204 = OpLabel
+               OpBranch %184
+        %184 = OpLabel
+               OpBranch %163
+        %163 = OpLabel
+               OpBranch %142
+        %142 = OpLabel
+               OpBranch %121
+        %121 = OpLabel
+               OpBranch %98
+         %98 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Add the facts "resolution.x == 250" and "resolution.y == 100".
+  protobufs::FactSequence facts;
+  {
+    protobufs::FactConstantUniform resolution_x_eq_250;
+    *resolution_x_eq_250.mutable_uniform_buffer_element_descriptor() =
+        MakeUniformBufferElementDescriptor(24, {0, 0});
+    *resolution_x_eq_250.mutable_constant_word()->Add() = 250;
+    protobufs::Fact temp;
+    *temp.mutable_constant_uniform_fact() = resolution_x_eq_250;
+    *facts.mutable_fact()->Add() = temp;
+  }
+  {
+    protobufs::FactConstantUniform resolution_y_eq_100;
+    *resolution_y_eq_100.mutable_uniform_buffer_element_descriptor() =
+        MakeUniformBufferElementDescriptor(24, {0, 1});
+    *resolution_y_eq_100.mutable_constant_word()->Add() = 100;
+    protobufs::Fact temp;
+    *temp.mutable_constant_uniform_fact() = resolution_y_eq_100;
+    *facts.mutable_fact()->Add() = temp;
+  }
+
+  // Do 10 fuzzer runs, starting from an initial seed of 94 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndReplayer(shader, facts, 94, 10);
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_type_pointer_test.cpp b/test/fuzz/transformation_add_type_pointer_test.cpp
new file mode 100644
index 0000000..184b699
--- /dev/null
+++ b/test/fuzz/transformation_add_type_pointer_test.cpp
@@ -0,0 +1,218 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_type_pointer.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddTypePointerTest, BasicTest) {
+  // The SPIR-V was obtained from this GLSL:
+  //
+  // #version 450
+  //
+  // int x;
+  // float y;
+  // vec2 z;
+  //
+  // struct T {
+  //   int a, b;
+  // };
+  //
+  // struct S {
+  //   T t;
+  //   int u;
+  // };
+  //
+  // void main() {
+  //   S myS = S(T(1, 2), 3);
+  //   myS.u = x;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %7 "T"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %8 "S"
+               OpMemberName %8 0 "t"
+               OpMemberName %8 1 "u"
+               OpName %10 "myS"
+               OpName %17 "x"
+               OpName %23 "y"
+               OpName %26 "z"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypeStruct %7 %6
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %6 1
+         %12 = OpConstant %6 2
+         %13 = OpConstantComposite %7 %11 %12
+         %14 = OpConstant %6 3
+         %15 = OpConstantComposite %8 %13 %14
+         %16 = OpTypePointer Private %6
+         %17 = OpVariable %16 Private
+         %19 = OpTypePointer Function %6
+         %21 = OpTypeFloat 32
+         %22 = OpTypePointer Private %21
+         %23 = OpVariable %22 Private
+         %24 = OpTypeVector %21 2
+         %25 = OpTypePointer Private %24
+         %26 = OpVariable %25 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %15
+         %18 = OpLoad %6 %17
+         %20 = OpAccessChain %19 %10 %11
+               OpStore %20 %18
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  auto bad_type_id_does_not_exist =
+      transformation::MakeTransformationAddTypePointer(
+          100, SpvStorageClassFunction, 101);
+  auto bad_type_id_is_not_type =
+      transformation::MakeTransformationAddTypePointer(
+          100, SpvStorageClassFunction, 23);
+  auto bad_result_id_is_not_fresh =
+      transformation::MakeTransformationAddTypePointer(
+          17, SpvStorageClassFunction, 21);
+
+  auto good_new_private_pointer_to_t =
+      transformation::MakeTransformationAddTypePointer(
+          101, SpvStorageClassPrivate, 7);
+  auto good_new_uniform_pointer_to_t =
+      transformation::MakeTransformationAddTypePointer(
+          102, SpvStorageClassUniform, 7);
+  auto good_another_function_pointer_to_s =
+      transformation::MakeTransformationAddTypePointer(
+          103, SpvStorageClassFunction, 8);
+  auto good_new_uniform_pointer_to_s =
+      transformation::MakeTransformationAddTypePointer(
+          104, SpvStorageClassUniform, 8);
+  auto good_another_private_pointer_to_float =
+      transformation::MakeTransformationAddTypePointer(
+          105, SpvStorageClassPrivate, 21);
+  auto good_new_private_pointer_to_private_pointer_to_float =
+      transformation::MakeTransformationAddTypePointer(
+          106, SpvStorageClassPrivate, 105);
+  auto good_new_uniform_pointer_to_vec2 =
+      transformation::MakeTransformationAddTypePointer(
+          107, SpvStorageClassUniform, 24);
+  auto good_new_private_pointer_to_uniform_pointer_to_vec2 =
+      transformation::MakeTransformationAddTypePointer(
+          108, SpvStorageClassPrivate, 107);
+
+  ASSERT_FALSE(transformation::IsApplicable(bad_type_id_does_not_exist,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(bad_type_id_is_not_type,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(bad_result_id_is_not_fresh,
+                                            context.get(), fact_manager));
+
+  for (auto& transformation :
+       {good_new_private_pointer_to_t, good_new_uniform_pointer_to_t,
+        good_another_function_pointer_to_s, good_new_uniform_pointer_to_s,
+        good_another_private_pointer_to_float,
+        good_new_private_pointer_to_private_pointer_to_float,
+        good_new_uniform_pointer_to_vec2,
+        good_new_private_pointer_to_uniform_pointer_to_vec2}) {
+    ASSERT_TRUE(transformation::IsApplicable(transformation, context.get(),
+                                             fact_manager));
+    transformation::Apply(transformation, context.get(), &fact_manager);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %7 "T"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %8 "S"
+               OpMemberName %8 0 "t"
+               OpMemberName %8 1 "u"
+               OpName %10 "myS"
+               OpName %17 "x"
+               OpName %23 "y"
+               OpName %26 "z"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypeStruct %7 %6
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %6 1
+         %12 = OpConstant %6 2
+         %13 = OpConstantComposite %7 %11 %12
+         %14 = OpConstant %6 3
+         %15 = OpConstantComposite %8 %13 %14
+         %16 = OpTypePointer Private %6
+         %17 = OpVariable %16 Private
+         %19 = OpTypePointer Function %6
+         %21 = OpTypeFloat 32
+         %22 = OpTypePointer Private %21
+         %23 = OpVariable %22 Private
+         %24 = OpTypeVector %21 2
+         %25 = OpTypePointer Private %24
+         %26 = OpVariable %25 Private
+        %101 = OpTypePointer Private %7
+        %102 = OpTypePointer Uniform %7
+        %103 = OpTypePointer Function %8
+        %104 = OpTypePointer Uniform %8
+        %105 = OpTypePointer Private %21
+        %106 = OpTypePointer Private %105
+        %107 = OpTypePointer Uniform %24
+        %108 = OpTypePointer Private %107
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %15
+         %18 = OpLoad %6 %17
+         %20 = OpAccessChain %19 %10 %11
+               OpStore %20 %18
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
new file mode 100644
index 0000000..b3a0ccd
--- /dev/null
+++ b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
@@ -0,0 +1,1486 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_constant_with_uniform.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+bool AddFactHelper(
+    FactManager* fact_manager, opt::IRContext* context, uint32_t word,
+    const protobufs::UniformBufferElementDescriptor& descriptor) {
+  protobufs::FactConstantUniform constant_uniform_fact;
+  constant_uniform_fact.add_constant_word(word);
+  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+      descriptor;
+  protobufs::Fact fact;
+  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+  return fact_manager->AddFact(fact, context);
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int a;
+  //   int b;
+  //   int c;
+  // };
+  //
+  // void main()
+  // {
+  //   int x;
+  //   x = 1;
+  //   x = x + 2;
+  //   x = 3 + x;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpIAdd %6 %10 %11
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+         %15 = OpIAdd %6 %14 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_a =
+      MakeUniformBufferElementDescriptor(18, {0});
+  protobufs::UniformBufferElementDescriptor blockname_b =
+      MakeUniformBufferElementDescriptor(18, {1});
+  protobufs::UniformBufferElementDescriptor blockname_c =
+      MakeUniformBufferElementDescriptor(18, {2});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_a));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_b));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, blockname_c));
+
+  // The constant ids are 9, 11 and 14, for 1, 2 and 3 respectively.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+  protobufs::IdUseDescriptor use_of_11_in_add =
+      transformation::MakeIdUseDescriptor(11, SpvOpIAdd, 1, 12, 0);
+  protobufs::IdUseDescriptor use_of_14_in_add =
+      transformation::MakeIdUseDescriptor(14, SpvOpIAdd, 0, 15, 0);
+
+  // These transformations work: they match the facts.
+  auto transformation_use_of_9_in_store =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_a, 100, 101);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_9_in_store,
+                                           context.get(), fact_manager));
+  auto transformation_use_of_11_in_add =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_11_in_add, blockname_b, 102, 103);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_11_in_add,
+                                           context.get(), fact_manager));
+  auto transformation_use_of_14_in_add =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_14_in_add, blockname_c, 104, 105);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_14_in_add,
+                                           context.get(), fact_manager));
+
+  // The transformations are not applicable if we change which uniforms are
+  // applied to which constants.
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_b, 101, 102),
+      context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_11_in_add, blockname_c, 101, 102),
+      context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_14_in_add, blockname_a, 101, 102),
+      context.get(), fact_manager));
+
+  // The following transformations do not apply because the uniform descriptors
+  // are not sensible.
+  protobufs::UniformBufferElementDescriptor nonsense_uniform_descriptor1 =
+      MakeUniformBufferElementDescriptor(19, {0});
+  protobufs::UniformBufferElementDescriptor nonsense_uniform_descriptor2 =
+      MakeUniformBufferElementDescriptor(18, {5});
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, nonsense_uniform_descriptor1, 101, 102),
+      context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, nonsense_uniform_descriptor2, 101, 102),
+      context.get(), fact_manager));
+
+  // The following transformation does not apply because the id descriptor is
+  // not sensible.
+  protobufs::IdUseDescriptor nonsense_id_use_descriptor =
+      transformation::MakeIdUseDescriptor(9, SpvOpIAdd, 0, 15, 0);
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          nonsense_id_use_descriptor, blockname_a, 101, 102),
+      context.get(), fact_manager));
+
+  // The following transformations do not apply because the ids are not fresh.
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_11_in_add, blockname_b, 15, 103),
+      context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_11_in_add, blockname_b, 102, 15),
+      context.get(), fact_manager));
+
+  // Apply the use of 9 in a store.
+  transformation::Apply(transformation_use_of_9_in_store, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  std::string after_replacing_use_of_9_in_store = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %18 %50
+        %101 = OpLoad %6 %100
+               OpStore %8 %101
+         %10 = OpLoad %6 %8
+         %12 = OpIAdd %6 %10 %11
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+         %15 = OpIAdd %6 %14 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_replacing_use_of_9_in_store, context.get()));
+
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_11_in_add,
+                                           context.get(), fact_manager));
+  // Apply the use of 11 in an add.
+  transformation::Apply(transformation_use_of_11_in_add, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  std::string after_replacing_use_of_11_in_add = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %18 %50
+        %101 = OpLoad %6 %100
+               OpStore %8 %101
+         %10 = OpLoad %6 %8
+        %102 = OpAccessChain %51 %18 %9
+        %103 = OpLoad %6 %102
+         %12 = OpIAdd %6 %10 %103
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+         %15 = OpIAdd %6 %14 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_replacing_use_of_11_in_add, context.get()));
+
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_14_in_add,
+                                           context.get(), fact_manager));
+  // Apply the use of 15 in an add.
+  transformation::Apply(transformation_use_of_14_in_add, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  std::string after_replacing_use_of_14_in_add = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %16 "blockname"
+               OpMemberName %16 0 "a"
+               OpMemberName %16 1 "b"
+               OpMemberName %16 2 "c"
+               OpName %18 ""
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpMemberDecorate %16 2 Offset 8
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpConstant %6 0
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %14 = OpConstant %6 3
+         %16 = OpTypeStruct %6 %6 %6
+         %17 = OpTypePointer Uniform %16
+         %51 = OpTypePointer Uniform %6
+         %18 = OpVariable %17 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %18 %50
+        %101 = OpLoad %6 %100
+               OpStore %8 %101
+         %10 = OpLoad %6 %8
+        %102 = OpAccessChain %51 %18 %9
+        %103 = OpLoad %6 %102
+         %12 = OpIAdd %6 %10 %103
+               OpStore %8 %12
+         %13 = OpLoad %6 %8
+        %104 = OpAccessChain %51 %18 %11
+        %105 = OpLoad %6 %104
+         %15 = OpIAdd %6 %105 %13
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_replacing_use_of_14_in_add, context.get()));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, NestedStruct) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // struct U {
+  //   int x; // == 4
+  // };
+  //
+  // struct T {
+  //   int x; // == 3
+  //   U y;
+  // };
+  //
+  // struct S {
+  //   T x;
+  //   int y; // == 2
+  // };
+  //
+  // uniform blockname {
+  //   int x; // == 1
+  //   S y;
+  // };
+  //
+  // void foo(int a) { }
+  //
+  // void main()
+  // {
+  //   int x;
+  //   x = 1;
+  //   x = x + 2;
+  //   x = 3 + x;
+  //   foo(4);
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %10 "foo(i1;"
+               OpName %9 "a"
+               OpName %12 "x"
+               OpName %21 "param"
+               OpName %23 "U"
+               OpMemberName %23 0 "x"
+               OpName %24 "T"
+               OpMemberName %24 0 "x"
+               OpMemberName %24 1 "y"
+               OpName %25 "S"
+               OpMemberName %25 0 "x"
+               OpMemberName %25 1 "y"
+               OpName %26 "blockname"
+               OpMemberName %26 0 "x"
+               OpMemberName %26 1 "y"
+               OpName %28 ""
+               OpMemberDecorate %23 0 Offset 0
+               OpMemberDecorate %24 0 Offset 0
+               OpMemberDecorate %24 1 Offset 16
+               OpMemberDecorate %25 0 Offset 0
+               OpMemberDecorate %25 1 Offset 32
+               OpMemberDecorate %26 0 Offset 0
+               OpMemberDecorate %26 1 Offset 16
+               OpDecorate %26 Block
+               OpDecorate %28 DescriptorSet 0
+               OpDecorate %28 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpConstant %6 0
+         %13 = OpConstant %6 1
+         %15 = OpConstant %6 2
+         %17 = OpConstant %6 3
+         %20 = OpConstant %6 4
+         %23 = OpTypeStruct %6
+         %24 = OpTypeStruct %6 %23
+         %25 = OpTypeStruct %24 %6
+         %26 = OpTypeStruct %6 %25
+         %27 = OpTypePointer Uniform %26
+         %51 = OpTypePointer Uniform %6
+         %28 = OpVariable %27 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpStore %12 %16
+         %18 = OpLoad %6 %12
+         %19 = OpIAdd %6 %17 %18
+               OpStore %12 %19
+               OpStore %21 %20
+         %22 = OpFunctionCall %2 %10 %21
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_1 =
+      MakeUniformBufferElementDescriptor(28, {0});
+  protobufs::UniformBufferElementDescriptor blockname_2 =
+      MakeUniformBufferElementDescriptor(28, {1, 1});
+  protobufs::UniformBufferElementDescriptor blockname_3 =
+      MakeUniformBufferElementDescriptor(28, {1, 0, 0});
+  protobufs::UniformBufferElementDescriptor blockname_4 =
+      MakeUniformBufferElementDescriptor(28, {1, 0, 1, 0});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_1));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_2));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, blockname_3));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 4, blockname_4));
+
+  // The constant ids are 13, 15, 17 and 20, for 1, 2, 3 and 4 respectively.
+  protobufs::IdUseDescriptor use_of_13_in_store =
+      transformation::MakeIdUseDescriptor(13, SpvOpStore, 1, 21, 0);
+  protobufs::IdUseDescriptor use_of_15_in_add =
+      transformation::MakeIdUseDescriptor(15, SpvOpIAdd, 1, 16, 0);
+  protobufs::IdUseDescriptor use_of_17_in_add =
+      transformation::MakeIdUseDescriptor(17, SpvOpIAdd, 0, 19, 0);
+  protobufs::IdUseDescriptor use_of_20_in_store =
+      transformation::MakeIdUseDescriptor(20, SpvOpStore, 1, 19, 1);
+
+  // These transformations work: they match the facts.
+  auto transformation_use_of_13_in_store =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_13_in_store, blockname_1, 100, 101);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_13_in_store,
+                                           context.get(), fact_manager));
+  auto transformation_use_of_15_in_add =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_15_in_add, blockname_2, 102, 103);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_15_in_add,
+                                           context.get(), fact_manager));
+  auto transformation_use_of_17_in_add =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_17_in_add, blockname_3, 104, 105);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_17_in_add,
+                                           context.get(), fact_manager));
+  auto transformation_use_of_20_in_store =
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_20_in_store, blockname_4, 106, 107);
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_20_in_store,
+                                           context.get(), fact_manager));
+
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_13_in_store,
+                                           context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_15_in_add,
+                                           context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_17_in_add,
+                                           context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_20_in_store,
+                                           context.get(), fact_manager));
+
+  transformation::Apply(transformation_use_of_13_in_store, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_13_in_store,
+                                            context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_15_in_add,
+                                           context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_17_in_add,
+                                           context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_20_in_store,
+                                           context.get(), fact_manager));
+
+  transformation::Apply(transformation_use_of_15_in_add, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_13_in_store,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_15_in_add,
+                                            context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_17_in_add,
+                                           context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_20_in_store,
+                                           context.get(), fact_manager));
+
+  transformation::Apply(transformation_use_of_17_in_add, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_13_in_store,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_15_in_add,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_17_in_add,
+                                            context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(transformation_use_of_20_in_store,
+                                           context.get(), fact_manager));
+
+  transformation::Apply(transformation_use_of_20_in_store, context.get(),
+                        &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_13_in_store,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_15_in_add,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_17_in_add,
+                                            context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(transformation_use_of_20_in_store,
+                                            context.get(), fact_manager));
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %10 "foo(i1;"
+               OpName %9 "a"
+               OpName %12 "x"
+               OpName %21 "param"
+               OpName %23 "U"
+               OpMemberName %23 0 "x"
+               OpName %24 "T"
+               OpMemberName %24 0 "x"
+               OpMemberName %24 1 "y"
+               OpName %25 "S"
+               OpMemberName %25 0 "x"
+               OpMemberName %25 1 "y"
+               OpName %26 "blockname"
+               OpMemberName %26 0 "x"
+               OpMemberName %26 1 "y"
+               OpName %28 ""
+               OpMemberDecorate %23 0 Offset 0
+               OpMemberDecorate %24 0 Offset 0
+               OpMemberDecorate %24 1 Offset 16
+               OpMemberDecorate %25 0 Offset 0
+               OpMemberDecorate %25 1 Offset 32
+               OpMemberDecorate %26 0 Offset 0
+               OpMemberDecorate %26 1 Offset 16
+               OpDecorate %26 Block
+               OpDecorate %28 DescriptorSet 0
+               OpDecorate %28 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %50 = OpConstant %6 0
+         %13 = OpConstant %6 1
+         %15 = OpConstant %6 2
+         %17 = OpConstant %6 3
+         %20 = OpConstant %6 4
+         %23 = OpTypeStruct %6
+         %24 = OpTypeStruct %6 %23
+         %25 = OpTypeStruct %24 %6
+         %26 = OpTypeStruct %6 %25
+         %27 = OpTypePointer Uniform %26
+         %51 = OpTypePointer Uniform %6
+         %28 = OpVariable %27 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+        %100 = OpAccessChain %51 %28 %50
+        %101 = OpLoad %6 %100
+               OpStore %12 %101
+         %14 = OpLoad %6 %12
+        %102 = OpAccessChain %51 %28 %13 %13
+        %103 = OpLoad %6 %102
+         %16 = OpIAdd %6 %14 %103
+               OpStore %12 %16
+         %18 = OpLoad %6 %12
+        %104 = OpAccessChain %51 %28 %13 %50 %50
+        %105 = OpLoad %6 %104
+         %19 = OpIAdd %6 %105 %18
+               OpStore %12 %19
+        %106 = OpAccessChain %51 %28 %13 %50 %13 %50
+        %107 = OpLoad %6 %106
+               OpStore %21 %107
+         %22 = OpFunctionCall %2 %10 %21
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, NoUniformIntPointerPresent) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int x; // == 0
+  // };
+  //
+  // void main()
+  // {
+  //   int a;
+  //   a = 0;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "blockname"
+               OpMemberName %10 0 "x"
+               OpName %12 ""
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_0 =
+      MakeUniformBufferElementDescriptor(12, {0});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 0, blockname_0));
+
+  // The constant id is 9 for 0.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+
+  // This transformation is not available because no uniform pointer to integer
+  // type is present:
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_0, 100, 101),
+      context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, NoConstantPresentForIndex) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int x; // == 0
+  //   int y; // == 9
+  // };
+  //
+  // void main()
+  // {
+  //   int a;
+  //   a = 9;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "blockname"
+               OpMemberName %10 0 "x"
+               OpMemberName %10 1 "y"
+               OpName %12 ""
+               OpMemberDecorate %10 0 Offset 0
+               OpMemberDecorate %10 1 Offset 4
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 9
+         %10 = OpTypeStruct %6 %6
+         %11 = OpTypePointer Uniform %10
+         %50 = OpTypePointer Uniform %6
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_0 =
+      MakeUniformBufferElementDescriptor(12, {0});
+  protobufs::UniformBufferElementDescriptor blockname_9 =
+      MakeUniformBufferElementDescriptor(12, {1});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
+
+  // The constant id is 9 for 9.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+
+  // This transformation is not available because no constant is present for the
+  // index 1 required to index into the uniform buffer:
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_9, 100, 101),
+      context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest,
+     NoIntTypePresentToEnableIndexing) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   float f; // == 9
+  // };
+  //
+  // void main()
+  // {
+  //   float a;
+  //   a = 3.0;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "blockname"
+               OpMemberName %10 0 "f"
+               OpName %12 ""
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_3 =
+      MakeUniformBufferElementDescriptor(12, {0});
+
+  uint32_t float_data[1];
+  float temp = 3.0;
+  memcpy(&float_data[0], &temp, sizeof(float));
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, context.get(), float_data[0], blockname_3));
+
+  // The constant id is 9 for 3.0.
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 8, 0);
+
+  // This transformation is not available because no integer type is present to
+  // allow a constant index to be expressed:
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_3, 100, 101),
+      context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest,
+     UniformFactsDoNotMatchConstants) {
+  // This test came from the following GLSL:
+  //
+  // #version 450
+  //
+  // uniform blockname {
+  //   int x; // == 9
+  //   int y; // == 10
+  // };
+  //
+  // void main()
+  // {
+  //   int a;
+  //   int b;
+  //   a = 9;
+  //   b = 10;
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %8 "a"
+               OpName %10 "b"
+               OpName %12 "blockname"
+               OpMemberName %12 0 "x"
+               OpMemberName %12 1 "y"
+               OpName %14 ""
+               OpMemberDecorate %12 0 Offset 0
+               OpMemberDecorate %12 1 Offset 4
+               OpDecorate %12 Block
+               OpDecorate %14 DescriptorSet 0
+               OpDecorate %14 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 9
+         %11 = OpConstant %6 10
+         %50 = OpConstant %6 0
+         %51 = OpConstant %6 1
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Uniform %12
+         %52 = OpTypePointer Uniform %6
+         %14 = OpVariable %13 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  protobufs::UniformBufferElementDescriptor blockname_9 =
+      MakeUniformBufferElementDescriptor(14, {0});
+  protobufs::UniformBufferElementDescriptor blockname_10 =
+      MakeUniformBufferElementDescriptor(14, {1});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 10, blockname_10));
+
+  // The constant ids for 9 and 10 are 9 and 11 respectively
+  protobufs::IdUseDescriptor use_of_9_in_store =
+      transformation::MakeIdUseDescriptor(9, SpvOpStore, 1, 10, 0);
+  protobufs::IdUseDescriptor use_of_11_in_store =
+      transformation::MakeIdUseDescriptor(11, SpvOpStore, 1, 10, 1);
+
+  // These are right:
+  ASSERT_TRUE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_9, 100, 101),
+      context.get(), fact_manager));
+  ASSERT_TRUE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_11_in_store, blockname_10, 102, 103),
+      context.get(), fact_manager));
+
+  // These are wrong because the constants do not match the facts about
+  // uniforms.
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_11_in_store, blockname_9, 100, 101),
+      context.get(), fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          use_of_9_in_store, blockname_10, 102, 103),
+      context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceConstantWithUniformTest, ComplexReplacements) {
+  // The following GLSL was the basis for this test:
+
+  // #version 450
+  //
+  // struct T {
+  //   float a[5]; // [1.0, 1.5, 1.75, 1.875, 1.9375]
+  //   ivec4 b; // (1, 2, 3, 4)
+  //   vec3 c; // (2.0, 2.5, 2.75)
+  //   uint d; // 42u
+  //   bool e; // Not used in test
+  // };
+  //
+  // uniform block {
+  //   T f;
+  //   int g; // 22
+  //   uvec2 h; // (100u, 200u)
+  // };
+  //
+  // void main()
+  // {
+  //   T myT;
+  //
+  //   myT.a[0] = 1.9375;
+  //   myT.a[1] = 1.875;
+  //   myT.a[2] = 1.75;
+  //   myT.a[3] = 1.5;
+  //   myT.a[4] = 1.0;
+  //
+  //   myT.b.x = 4;
+  //   myT.b.y = 3;
+  //   myT.b.z = 2;
+  //   myT.b.w = 1;
+  //
+  //   myT.b.r = 22;
+  //
+  //   myT.c[0] = 2.75;
+  //   myT.c[0] = 2.5;
+  //   myT.c[0] = 2.0;
+  //
+  //   myT.d = 42u;
+  //   myT.d = 100u;
+  //   myT.d = 200u;
+  //
+  //   myT.e = true; // No attempt to replace 'true' by a uniform value
+  //
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %14 "T"
+               OpMemberName %14 0 "a"
+               OpMemberName %14 1 "b"
+               OpMemberName %14 2 "c"
+               OpMemberName %14 3 "d"
+               OpMemberName %14 4 "e"
+               OpName %16 "myT"
+               OpName %61 "T"
+               OpMemberName %61 0 "a"
+               OpMemberName %61 1 "b"
+               OpMemberName %61 2 "c"
+               OpMemberName %61 3 "d"
+               OpMemberName %61 4 "e"
+               OpName %63 "block"
+               OpMemberName %63 0 "f"
+               OpMemberName %63 1 "g"
+               OpMemberName %63 2 "h"
+               OpName %65 ""
+               OpDecorate %60 ArrayStride 16
+               OpMemberDecorate %61 0 Offset 0
+               OpMemberDecorate %61 1 Offset 80
+               OpMemberDecorate %61 2 Offset 96
+               OpMemberDecorate %61 3 Offset 108
+               OpMemberDecorate %61 4 Offset 112
+               OpMemberDecorate %63 0 Offset 0
+               OpMemberDecorate %63 1 Offset 128
+               OpMemberDecorate %63 2 Offset 136
+               OpDecorate %63 Block
+               OpDecorate %65 DescriptorSet 0
+               OpDecorate %65 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 5
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 4
+         %12 = OpTypeVector %6 3
+         %13 = OpTypeBool
+         %14 = OpTypeStruct %9 %11 %12 %7 %13
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %10 0
+         %18 = OpConstant %6 1.9375
+         %19 = OpTypePointer Function %6
+         %21 = OpConstant %10 1
+         %22 = OpConstant %6 1.875
+         %24 = OpConstant %10 2
+         %25 = OpConstant %6 1.75
+         %27 = OpConstant %10 3
+         %28 = OpConstant %6 1.5
+         %30 = OpConstant %10 4
+         %31 = OpConstant %6 1
+         %33 = OpConstant %7 0
+         %34 = OpTypePointer Function %10
+         %36 = OpConstant %7 1
+         %38 = OpConstant %7 2
+         %40 = OpConstant %7 3
+         %42 = OpConstant %10 22
+         %44 = OpConstant %6 2.75
+         %46 = OpConstant %6 2.5
+         %48 = OpConstant %6 2
+         %50 = OpConstant %7 42
+         %51 = OpTypePointer Function %7
+         %53 = OpConstant %7 100
+         %55 = OpConstant %7 200
+         %57 = OpConstantTrue %13
+         %58 = OpTypePointer Function %13
+         %60 = OpTypeArray %6 %8
+         %61 = OpTypeStruct %60 %11 %12 %7 %7
+         %62 = OpTypeVector %7 2
+         %63 = OpTypeStruct %61 %10 %62
+         %64 = OpTypePointer Uniform %63
+        %100 = OpTypePointer Uniform %10
+        %101 = OpTypePointer Uniform %7
+        %102 = OpTypePointer Uniform %6
+        %103 = OpTypePointer Uniform %13
+         %65 = OpVariable %64 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %16 = OpVariable %15 Function
+         %20 = OpAccessChain %19 %16 %17 %17
+               OpStore %20 %18
+         %23 = OpAccessChain %19 %16 %17 %21
+               OpStore %23 %22
+         %26 = OpAccessChain %19 %16 %17 %24
+               OpStore %26 %25
+         %29 = OpAccessChain %19 %16 %17 %27
+               OpStore %29 %28
+         %32 = OpAccessChain %19 %16 %17 %30
+               OpStore %32 %31
+         %35 = OpAccessChain %34 %16 %21 %33
+               OpStore %35 %30
+         %37 = OpAccessChain %34 %16 %21 %36
+               OpStore %37 %27
+         %39 = OpAccessChain %34 %16 %21 %38
+               OpStore %39 %24
+         %41 = OpAccessChain %34 %16 %21 %40
+               OpStore %41 %21
+         %43 = OpAccessChain %34 %16 %21 %33
+               OpStore %43 %42
+         %45 = OpAccessChain %19 %16 %24 %33
+               OpStore %45 %44
+         %47 = OpAccessChain %19 %16 %24 %33
+               OpStore %47 %46
+         %49 = OpAccessChain %19 %16 %24 %33
+               OpStore %49 %48
+         %52 = OpAccessChain %51 %16 %27
+               OpStore %52 %50
+         %54 = OpAccessChain %51 %16 %27
+               OpStore %54 %53
+         %56 = OpAccessChain %51 %16 %27
+               OpStore %56 %55
+         %59 = OpAccessChain %58 %16 %30
+               OpStore %59 %57
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  const float float_array_values[5] = {1.0, 1.5, 1.75, 1.875, 1.9375};
+  uint32_t float_array_data[5];
+  memcpy(&float_array_data, &float_array_values, sizeof(float_array_values));
+
+  const float float_vector_values[3] = {2.0, 2.5, 2.75};
+  uint32_t float_vector_data[3];
+  memcpy(&float_vector_data, &float_vector_values, sizeof(float_vector_values));
+
+  protobufs::UniformBufferElementDescriptor uniform_f_a_0 =
+      MakeUniformBufferElementDescriptor(65, {0, 0, 0});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_1 =
+      MakeUniformBufferElementDescriptor(65, {0, 0, 1});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_2 =
+      MakeUniformBufferElementDescriptor(65, {0, 0, 2});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_3 =
+      MakeUniformBufferElementDescriptor(65, {0, 0, 3});
+  protobufs::UniformBufferElementDescriptor uniform_f_a_4 =
+      MakeUniformBufferElementDescriptor(65, {0, 0, 4});
+
+  protobufs::UniformBufferElementDescriptor uniform_f_b_x =
+      MakeUniformBufferElementDescriptor(65, {0, 1, 0});
+  protobufs::UniformBufferElementDescriptor uniform_f_b_y =
+      MakeUniformBufferElementDescriptor(65, {0, 1, 1});
+  protobufs::UniformBufferElementDescriptor uniform_f_b_z =
+      MakeUniformBufferElementDescriptor(65, {0, 1, 2});
+  protobufs::UniformBufferElementDescriptor uniform_f_b_w =
+      MakeUniformBufferElementDescriptor(65, {0, 1, 3});
+
+  protobufs::UniformBufferElementDescriptor uniform_f_c_x =
+      MakeUniformBufferElementDescriptor(65, {0, 2, 0});
+  protobufs::UniformBufferElementDescriptor uniform_f_c_y =
+      MakeUniformBufferElementDescriptor(65, {0, 2, 1});
+  protobufs::UniformBufferElementDescriptor uniform_f_c_z =
+      MakeUniformBufferElementDescriptor(65, {0, 2, 2});
+
+  protobufs::UniformBufferElementDescriptor uniform_f_d =
+      MakeUniformBufferElementDescriptor(65, {0, 3});
+
+  protobufs::UniformBufferElementDescriptor uniform_g =
+      MakeUniformBufferElementDescriptor(65, {1});
+
+  protobufs::UniformBufferElementDescriptor uniform_h_x =
+      MakeUniformBufferElementDescriptor(65, {2, 0});
+  protobufs::UniformBufferElementDescriptor uniform_h_y =
+      MakeUniformBufferElementDescriptor(65, {2, 1});
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[0],
+                            uniform_f_a_0));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[1],
+                            uniform_f_a_1));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[2],
+                            uniform_f_a_2));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[3],
+                            uniform_f_a_3));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[4],
+                            uniform_f_a_4));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, uniform_f_b_x));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, uniform_f_b_y));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 3, uniform_f_b_z));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 4, uniform_f_b_w));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[0],
+                            uniform_f_c_x));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[1],
+                            uniform_f_c_y));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_vector_data[2],
+                            uniform_f_c_z));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 42, uniform_f_d));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 22, uniform_g));
+
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 100, uniform_h_x));
+  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 200, uniform_h_y));
+
+  std::vector<protobufs::TransformationReplaceConstantWithUniform>
+      transformations;
+
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(18, SpvOpStore, 1, 20, 0),
+          uniform_f_a_4, 200, 201));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(22, SpvOpStore, 1, 23, 0),
+          uniform_f_a_3, 202, 203));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(25, SpvOpStore, 1, 26, 0),
+          uniform_f_a_2, 204, 205));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(28, SpvOpStore, 1, 29, 0),
+          uniform_f_a_1, 206, 207));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(31, SpvOpStore, 1, 32, 0),
+          uniform_f_a_0, 208, 209));
+
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(30, SpvOpStore, 1, 35, 0),
+          uniform_f_b_w, 210, 211));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(27, SpvOpStore, 1, 37, 0),
+          uniform_f_b_z, 212, 213));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(24, SpvOpStore, 1, 39, 0),
+          uniform_f_b_y, 214, 215));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(21, SpvOpStore, 1, 41, 0),
+          uniform_f_b_x, 216, 217));
+
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(44, SpvOpStore, 1, 45, 0),
+          uniform_f_c_z, 220, 221));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(46, SpvOpStore, 1, 47, 0),
+          uniform_f_c_y, 222, 223));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(48, SpvOpStore, 1, 49, 0),
+          uniform_f_c_x, 224, 225));
+
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(50, SpvOpStore, 1, 52, 0),
+          uniform_f_d, 226, 227));
+
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(53, SpvOpStore, 1, 54, 0),
+          uniform_h_x, 228, 229));
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(55, SpvOpStore, 1, 56, 0),
+          uniform_h_y, 230, 231));
+
+  transformations.emplace_back(
+      transformation::MakeTransformationReplaceConstantWithUniform(
+          transformation::MakeIdUseDescriptor(42, SpvOpStore, 1, 43, 0),
+          uniform_g, 218, 219));
+
+  for (auto& transformation : transformations) {
+    ASSERT_TRUE(transformation::IsApplicable(transformation, context.get(),
+                                             fact_manager));
+    transformation::Apply(transformation, context.get(), &fact_manager);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpName %14 "T"
+               OpMemberName %14 0 "a"
+               OpMemberName %14 1 "b"
+               OpMemberName %14 2 "c"
+               OpMemberName %14 3 "d"
+               OpMemberName %14 4 "e"
+               OpName %16 "myT"
+               OpName %61 "T"
+               OpMemberName %61 0 "a"
+               OpMemberName %61 1 "b"
+               OpMemberName %61 2 "c"
+               OpMemberName %61 3 "d"
+               OpMemberName %61 4 "e"
+               OpName %63 "block"
+               OpMemberName %63 0 "f"
+               OpMemberName %63 1 "g"
+               OpMemberName %63 2 "h"
+               OpName %65 ""
+               OpDecorate %60 ArrayStride 16
+               OpMemberDecorate %61 0 Offset 0
+               OpMemberDecorate %61 1 Offset 80
+               OpMemberDecorate %61 2 Offset 96
+               OpMemberDecorate %61 3 Offset 108
+               OpMemberDecorate %61 4 Offset 112
+               OpMemberDecorate %63 0 Offset 0
+               OpMemberDecorate %63 1 Offset 128
+               OpMemberDecorate %63 2 Offset 136
+               OpDecorate %63 Block
+               OpDecorate %65 DescriptorSet 0
+               OpDecorate %65 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 5
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 4
+         %12 = OpTypeVector %6 3
+         %13 = OpTypeBool
+         %14 = OpTypeStruct %9 %11 %12 %7 %13
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %10 0
+         %18 = OpConstant %6 1.9375
+         %19 = OpTypePointer Function %6
+         %21 = OpConstant %10 1
+         %22 = OpConstant %6 1.875
+         %24 = OpConstant %10 2
+         %25 = OpConstant %6 1.75
+         %27 = OpConstant %10 3
+         %28 = OpConstant %6 1.5
+         %30 = OpConstant %10 4
+         %31 = OpConstant %6 1
+         %33 = OpConstant %7 0
+         %34 = OpTypePointer Function %10
+         %36 = OpConstant %7 1
+         %38 = OpConstant %7 2
+         %40 = OpConstant %7 3
+         %42 = OpConstant %10 22
+         %44 = OpConstant %6 2.75
+         %46 = OpConstant %6 2.5
+         %48 = OpConstant %6 2
+         %50 = OpConstant %7 42
+         %51 = OpTypePointer Function %7
+         %53 = OpConstant %7 100
+         %55 = OpConstant %7 200
+         %57 = OpConstantTrue %13
+         %58 = OpTypePointer Function %13
+         %60 = OpTypeArray %6 %8
+         %61 = OpTypeStruct %60 %11 %12 %7 %7
+         %62 = OpTypeVector %7 2
+         %63 = OpTypeStruct %61 %10 %62
+         %64 = OpTypePointer Uniform %63
+        %100 = OpTypePointer Uniform %10
+        %101 = OpTypePointer Uniform %7
+        %102 = OpTypePointer Uniform %6
+        %103 = OpTypePointer Uniform %13
+         %65 = OpVariable %64 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %16 = OpVariable %15 Function
+         %20 = OpAccessChain %19 %16 %17 %17
+        %200 = OpAccessChain %102 %65 %17 %17 %30
+        %201 = OpLoad %6 %200
+               OpStore %20 %201
+         %23 = OpAccessChain %19 %16 %17 %21
+        %202 = OpAccessChain %102 %65 %17 %17 %27
+        %203 = OpLoad %6 %202
+               OpStore %23 %203
+         %26 = OpAccessChain %19 %16 %17 %24
+        %204 = OpAccessChain %102 %65 %17 %17 %24
+        %205 = OpLoad %6 %204
+               OpStore %26 %205
+         %29 = OpAccessChain %19 %16 %17 %27
+        %206 = OpAccessChain %102 %65 %17 %17 %21
+        %207 = OpLoad %6 %206
+               OpStore %29 %207
+         %32 = OpAccessChain %19 %16 %17 %30
+        %208 = OpAccessChain %102 %65 %17 %17 %17
+        %209 = OpLoad %6 %208
+               OpStore %32 %209
+         %35 = OpAccessChain %34 %16 %21 %33
+        %210 = OpAccessChain %100 %65 %17 %21 %27
+        %211 = OpLoad %10 %210
+               OpStore %35 %211
+         %37 = OpAccessChain %34 %16 %21 %36
+        %212 = OpAccessChain %100 %65 %17 %21 %24
+        %213 = OpLoad %10 %212
+               OpStore %37 %213
+         %39 = OpAccessChain %34 %16 %21 %38
+        %214 = OpAccessChain %100 %65 %17 %21 %21
+        %215 = OpLoad %10 %214
+               OpStore %39 %215
+         %41 = OpAccessChain %34 %16 %21 %40
+        %216 = OpAccessChain %100 %65 %17 %21 %17
+        %217 = OpLoad %10 %216
+               OpStore %41 %217
+         %43 = OpAccessChain %34 %16 %21 %33
+        %218 = OpAccessChain %100 %65 %21
+        %219 = OpLoad %10 %218
+               OpStore %43 %219
+         %45 = OpAccessChain %19 %16 %24 %33
+        %220 = OpAccessChain %102 %65 %17 %24 %24
+        %221 = OpLoad %6 %220
+               OpStore %45 %221
+         %47 = OpAccessChain %19 %16 %24 %33
+        %222 = OpAccessChain %102 %65 %17 %24 %21
+        %223 = OpLoad %6 %222
+               OpStore %47 %223
+         %49 = OpAccessChain %19 %16 %24 %33
+        %224 = OpAccessChain %102 %65 %17 %24 %17
+        %225 = OpLoad %6 %224
+               OpStore %49 %225
+         %52 = OpAccessChain %51 %16 %27
+        %226 = OpAccessChain %101 %65 %17 %27
+        %227 = OpLoad %7 %226
+               OpStore %52 %227
+         %54 = OpAccessChain %51 %16 %27
+        %228 = OpAccessChain %101 %65 %24 %17
+        %229 = OpLoad %7 %228
+               OpStore %54 %229
+         %56 = OpAccessChain %51 %16 %27
+        %230 = OpAccessChain %101 %65 %24 %21
+        %231 = OpLoad %7 %230
+               OpStore %56 %231
+         %59 = OpAccessChain %58 %16 %30
+               OpStore %59 %57
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools