Represent uniform facts via descriptor set and binding. (#2681)

* Represent uniform facts via descriptor set and binding.

Previously uniform facts were expressed with resepect to the id of a
uniform variable.  Describing them with respect to a descriptor set
and binding is more convenient from the point of view of expressing
facts about a shader without requiring analysis of its SPIR-V.

* Fix equality testing for uniform buffer element descriptors.

The equality test now checks that the lengths of the index vectors
match.  Added a test that exposes the previous omission.
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
index 407881b..9cb6896 100644
--- a/source/fuzz/fact_manager.cpp
+++ b/source/fuzz/fact_manager.cpp
@@ -201,20 +201,21 @@
 
 bool FactManager::ConstantUniformFacts::AddFact(
     const protobufs::FactConstantUniform& fact, opt::IRContext* context) {
-  auto should_be_uniform_variable = context->get_def_use_mgr()->GetDef(
-      fact.uniform_buffer_element_descriptor().uniform_variable_id());
-  if (!should_be_uniform_variable) {
+  // Try to find a unique instruction that declares a variable such that the
+  // variable is decorated with the descriptor set and binding associated with
+  // the constant uniform fact.
+  opt::Instruction* uniform_variable = FindUniformVariable(
+      fact.uniform_buffer_element_descriptor(), context, true);
+
+  if (!uniform_variable) {
     return false;
   }
-  if (SpvOpVariable != should_be_uniform_variable->opcode()) {
-    return false;
-  }
-  if (SpvStorageClassUniform !=
-      should_be_uniform_variable->GetSingleWordInOperand(0)) {
-    return false;
-  }
+
+  assert(SpvOpVariable == uniform_variable->opcode());
+  assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
+
   auto should_be_uniform_pointer_type =
-      context->get_type_mgr()->GetType(should_be_uniform_variable->type_id());
+      context->get_type_mgr()->GetType(uniform_variable->type_id());
   if (!should_be_uniform_pointer_type->AsPointer()) {
     return false;
   }
@@ -223,7 +224,7 @@
     return false;
   }
   auto should_be_uniform_pointer_instruction =
-      context->get_def_use_mgr()->GetDef(should_be_uniform_variable->type_id());
+      context->get_def_use_mgr()->GetDef(uniform_variable->type_id());
   auto element_type =
       should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
 
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 941a2ca..aecd4c7 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -64,8 +64,9 @@
   // is contained, and (b) a series of indices that need to be followed to get
   // to the element (via fields and array/vector indices).
   //
-  // Example: suppose %42 is the id of a uniform variable, and that the uniform
-  // variable has the following type (using GLSL-like syntax):
+  // Example: suppose there is a uniform variable with descriptor set 7 and
+  // binding 9, and that the uniform variable has the following type (using
+  // GLSL-like syntax):
   //
   // struct S {
   //   float f;
@@ -74,16 +75,17 @@
   // };
   //
   // Then:
-  // - 42[0] describes the 'f' field.
-  // - 42[1,1] describes the y component of the 'g' field.
-  // - 42[2,7,3] describes the w component of element 7 of the 'h' field
+  // - (7, 9, [0]) describes the 'f' field.
+  // - (7, 9, [1,1]) describes the y component of the 'g' field.
+  // - (7, 9, [2,7,3]) describes the w component of element 7 of the 'h' field
 
-  // The result id of a uniform variable.
-  uint32 uniform_variable_id = 1;
+  // The descriptor set and binding associated with a uniform variable.
+  uint32 descriptor_set = 1;
+  uint32 binding = 2;
 
   // An ordered sequence of indices through composite structures in the
   // uniform buffer.
-  repeated uint32 index = 2;
+  repeated uint32 index = 3;
 
 }
 
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp
index fce458c..cc6e137 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.cpp
+++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp
@@ -15,6 +15,7 @@
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 
 #include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -28,10 +29,12 @@
   // The input operands for the access chain.
   opt::Instruction::OperandList operands_for_access_chain;
 
+  opt::Instruction* uniform_variable =
+      FindUniformVariable(message.uniform_descriptor(), context, false);
+
   // 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()}});
+      {SPV_OPERAND_TYPE_ID, {uniform_variable->result_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;
diff --git a/source/fuzz/uniform_buffer_element_descriptor.cpp b/source/fuzz/uniform_buffer_element_descriptor.cpp
index cd840d3..8c758e4 100644
--- a/source/fuzz/uniform_buffer_element_descriptor.cpp
+++ b/source/fuzz/uniform_buffer_element_descriptor.cpp
@@ -14,13 +14,17 @@
 
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 
+#include <source/opt/instruction.h>
+
 namespace spvtools {
 namespace fuzz {
 
 protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
-    uint32_t uniform_variable_id, std::vector<uint32_t>&& indices) {
+    uint32_t descriptor_set, uint32_t binding,
+    std::vector<uint32_t>&& indices) {
   protobufs::UniformBufferElementDescriptor result;
-  result.set_uniform_variable_id(uniform_variable_id);
+  result.set_descriptor_set(descriptor_set);
+  result.set_binding(binding);
   for (auto index : indices) {
     result.add_index(index);
   }
@@ -30,10 +34,85 @@
 bool UniformBufferElementDescriptorEquals::operator()(
     const protobufs::UniformBufferElementDescriptor* first,
     const protobufs::UniformBufferElementDescriptor* second) const {
-  return first->uniform_variable_id() == second->uniform_variable_id() &&
+  return first->descriptor_set() == second->descriptor_set() &&
+         first->binding() == second->binding() &&
+         first->index().size() == second->index().size() &&
          std::equal(first->index().begin(), first->index().end(),
                     second->index().begin());
 }
 
+opt::Instruction* FindUniformVariable(
+    const protobufs::UniformBufferElementDescriptor&
+        uniform_buffer_element_descriptor,
+    opt::IRContext* context, bool check_unique) {
+  opt::Instruction* result = nullptr;
+
+  for (auto& inst : context->types_values()) {
+    // Consider all global variables with uniform storage class.
+    if (inst.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (inst.GetSingleWordInOperand(0) != SpvStorageClassUniform) {
+      continue;
+    }
+
+    // Determine whether the variable is decorated with a descriptor set
+    // matching that in |uniform_buffer_element|.
+    bool descriptor_set_matches = false;
+    context->get_decoration_mgr()->ForEachDecoration(
+        inst.result_id(), SpvDecorationDescriptorSet,
+        [&descriptor_set_matches, &uniform_buffer_element_descriptor](
+            const opt::Instruction& decoration_inst) {
+          const uint32_t kDescriptorSetOperandIndex = 2;
+          if (decoration_inst.GetSingleWordInOperand(
+                  kDescriptorSetOperandIndex) ==
+              uniform_buffer_element_descriptor.descriptor_set()) {
+            descriptor_set_matches = true;
+          }
+        });
+    if (!descriptor_set_matches) {
+      // Descriptor set does not match.
+      continue;
+    }
+
+    // Determine whether the variable is decorated with a binding matching that
+    // in |uniform_buffer_element|.
+    bool binding_matches = false;
+    context->get_decoration_mgr()->ForEachDecoration(
+        inst.result_id(), SpvDecorationBinding,
+        [&binding_matches, &uniform_buffer_element_descriptor](
+            const opt::Instruction& decoration_inst) {
+          const uint32_t kBindingOperandIndex = 2;
+          if (decoration_inst.GetSingleWordInOperand(kBindingOperandIndex) ==
+              uniform_buffer_element_descriptor.binding()) {
+            binding_matches = true;
+          }
+        });
+    if (!binding_matches) {
+      // Binding does not match.
+      continue;
+    }
+
+    // This instruction is a uniform variable with the right descriptor set and
+    // binding.
+    if (!check_unique) {
+      // If we aren't checking uniqueness, return it.
+      return &inst;
+    }
+
+    if (result) {
+      // More than one uniform variable is decorated with the given descriptor
+      // set and binding. This means the fact is ambiguous.
+      return nullptr;
+    }
+    result = &inst;
+  }
+
+  // We get here either if no match was found, or if |check_unique| holds and
+  // exactly one match was found.
+  assert(result == nullptr || check_unique);
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/uniform_buffer_element_descriptor.h b/source/fuzz/uniform_buffer_element_descriptor.h
index b2e5962..23a16f0 100644
--- a/source/fuzz/uniform_buffer_element_descriptor.h
+++ b/source/fuzz/uniform_buffer_element_descriptor.h
@@ -19,6 +19,8 @@
 #include <vector>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -26,7 +28,7 @@
 // Factory method to create a uniform buffer element descriptor message from an
 // id and list of indices.
 protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor(
-    uint32_t uniform_variable_id, std::vector<uint32_t>&& indices);
+    uint32_t descriptor_set, uint32_t binding, std::vector<uint32_t>&& indices);
 
 // Equality function for uniform buffer element descriptors.
 struct UniformBufferElementDescriptorEquals {
@@ -35,6 +37,16 @@
       const protobufs::UniformBufferElementDescriptor* second) const;
 };
 
+// Returns a pointer to an OpVariable in |context| that is decorated with the
+// descriptor set and binding associated with |uniform_buffer_element|.  Returns
+// nullptr if no such variable exists.  If multiple such variables exist, a
+// pointer to an arbitrary one of the associated instructions is returned if
+// |check_unique| is false, and nullptr is returned if |check_unique| is true.
+opt::Instruction* FindUniformVariable(
+    const protobufs::UniformBufferElementDescriptor&
+        uniform_buffer_element_descriptor,
+    opt::IRContext* context, bool check_unique);
+
 }  // namespace fuzz
 }  // namespace spvtools
 
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 454206c..7b47b55 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -31,7 +31,8 @@
           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)
+          transformation_split_block_test.cpp
+          uniform_buffer_element_descriptor_test.cpp)
 
   add_spvtools_unittest(TARGET fuzz
         SRCS ${SOURCES}
diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp
index bf07f35..738f8c9 100644
--- a/test/fuzz/fact_manager_test.cpp
+++ b/test/fuzz/fact_manager_test.cpp
@@ -58,6 +58,44 @@
                OpExecutionMode %4 OriginUpperLeft
                OpSource GLSL 450
                OpName %4 "main"
+               OpDecorate %100 DescriptorSet 0
+               OpDecorate %100 Binding 0
+               OpDecorate %200 DescriptorSet 0
+               OpDecorate %200 Binding 1
+               OpDecorate %300 DescriptorSet 0
+               OpDecorate %300 Binding 2
+               OpDecorate %400 DescriptorSet 0
+               OpDecorate %400 Binding 3
+               OpDecorate %500 DescriptorSet 0
+               OpDecorate %500 Binding 4
+               OpDecorate %600 DescriptorSet 0
+               OpDecorate %600 Binding 5
+               OpDecorate %700 DescriptorSet 0
+               OpDecorate %700 Binding 6
+               OpDecorate %800 DescriptorSet 1
+               OpDecorate %800 Binding 0
+               OpDecorate %900 DescriptorSet 1
+               OpDecorate %900 Binding 1
+               OpDecorate %1000 DescriptorSet 1
+               OpDecorate %1000 Binding 2
+               OpDecorate %1100 DescriptorSet 1
+               OpDecorate %1100 Binding 3
+               OpDecorate %1200 DescriptorSet 1
+               OpDecorate %1200 Binding 4
+               OpDecorate %1300 DescriptorSet 1
+               OpDecorate %1300 Binding 5
+               OpDecorate %1400 DescriptorSet 1
+               OpDecorate %1400 Binding 6
+               OpDecorate %1500 DescriptorSet 2
+               OpDecorate %1500 Binding 0
+               OpDecorate %1600 DescriptorSet 2
+               OpDecorate %1600 Binding 1
+               OpDecorate %1700 DescriptorSet 2
+               OpDecorate %1700 Binding 2
+               OpDecorate %1800 DescriptorSet 2
+               OpDecorate %1800 Binding 3
+               OpDecorate %1900 DescriptorSet 2
+               OpDecorate %1900 Binding 4
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
          %10 = OpTypeInt 32 0
@@ -231,94 +269,98 @@
                                                             type_uint32_id)
                   .empty());
 
+  // In the comments that follow we write v[...][...] to refer to uniform
+  // variable v indexed with some given indices, when in practice v is
+  // identified via a (descriptor set, binding) pair.
+
   // 100[2][3] == int(1)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
-                            MakeUniformBufferElementDescriptor(100, {2, 3})));
+                            MakeUniformBufferElementDescriptor(0, 0, {2, 3})));
 
   // 200[1][2][3] == int(1)
   ASSERT_TRUE(
       AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(200, {1, 2, 3})));
+                    MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3})));
 
   // 300[1][0][2][3] == int(1)
   ASSERT_TRUE(
       AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(300, {1, 0, 2, 3})));
+                    MakeUniformBufferElementDescriptor(0, 2, {1, 0, 2, 3})));
 
   // 400[2][3] = int32_min
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
-                            MakeUniformBufferElementDescriptor(400, {2, 3})));
+                            MakeUniformBufferElementDescriptor(0, 3, {2, 3})));
 
   // 500[1][2][3] = int32_min
   ASSERT_TRUE(
       AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
-                    MakeUniformBufferElementDescriptor(500, {1, 2, 3})));
+                    MakeUniformBufferElementDescriptor(0, 4, {1, 2, 3})));
 
   // 600[1][2][3] = int64_max
   ASSERT_TRUE(AddFactHelper(
       &fact_manager, context.get(), {buffer_int64_max[0], buffer_int64_max[1]},
-      MakeUniformBufferElementDescriptor(600, {1, 2, 3})));
+      MakeUniformBufferElementDescriptor(0, 5, {1, 2, 3})));
 
   // 700[1][1] = int64_max
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
                             {buffer_int64_max[0], buffer_int64_max[1]},
-                            MakeUniformBufferElementDescriptor(700, {1, 1})));
+                            MakeUniformBufferElementDescriptor(0, 6, {1, 1})));
 
   // 800[2][3] = uint(1)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
-                            MakeUniformBufferElementDescriptor(800, {2, 3})));
+                            MakeUniformBufferElementDescriptor(1, 0, {2, 3})));
 
   // 900[1][2][3] = uint(1)
   ASSERT_TRUE(
       AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(900, {1, 2, 3})));
+                    MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3})));
 
   // 1000[1][0][2][3] = uint(1)
   ASSERT_TRUE(
       AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(1000, {1, 0, 2, 3})));
+                    MakeUniformBufferElementDescriptor(1, 2, {1, 0, 2, 3})));
 
   // 1100[0] = uint64(1)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
                             {buffer_uint64_1[0], buffer_uint64_1[1]},
-                            MakeUniformBufferElementDescriptor(1100, {0})));
+                            MakeUniformBufferElementDescriptor(1, 3, {0})));
 
   // 1200[0][0] = uint64_max
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
                             {buffer_uint64_max[0], buffer_uint64_max[1]},
-                            MakeUniformBufferElementDescriptor(1200, {0, 0})));
+                            MakeUniformBufferElementDescriptor(1, 4, {0, 0})));
 
   // 1300[1][0] = uint64_max
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
                             {buffer_uint64_max[0], buffer_uint64_max[1]},
-                            MakeUniformBufferElementDescriptor(1300, {1, 0})));
+                            MakeUniformBufferElementDescriptor(1, 5, {1, 0})));
 
   // 1400[6] = float(10.0)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
-                            MakeUniformBufferElementDescriptor(1400, {6})));
+                            MakeUniformBufferElementDescriptor(1, 6, {6})));
 
   // 1500[7] = float(10.0)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
-                            MakeUniformBufferElementDescriptor(1500, {7})));
+                            MakeUniformBufferElementDescriptor(2, 0, {7})));
 
   // 1600[9][9] = float(10.0)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
-                            MakeUniformBufferElementDescriptor(1600, {9, 9})));
+                            MakeUniformBufferElementDescriptor(2, 1, {9, 9})));
 
   // 1700[9][9][1] = double(10.0)
   ASSERT_TRUE(AddFactHelper(
       &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
-      MakeUniformBufferElementDescriptor(1700, {9, 9, 1})));
+      MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1})));
 
   // 1800[9][9][2] = double(10.0)
   ASSERT_TRUE(AddFactHelper(
       &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
-      MakeUniformBufferElementDescriptor(1800, {9, 9, 2})));
+      MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2})));
 
   // 1900[0][0][0][0][0] = double(20.0)
   ASSERT_TRUE(AddFactHelper(
       &fact_manager, context.get(), {buffer_double_20[0], buffer_double_20[1]},
-      MakeUniformBufferElementDescriptor(1900, {0, 0, 0, 0, 0})));
+      MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0})));
 
   opt::Instruction::OperandList operands = {
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
@@ -429,12 +471,12 @@
           context.get(), double_constant_ids[0]);
   ASSERT_EQ(2, descriptors_for_double_10.size());
   {
-    auto temp = MakeUniformBufferElementDescriptor(1700, {9, 9, 1});
+    auto temp = MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1});
     ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
         &temp, &descriptors_for_double_10[0]));
   }
   {
-    auto temp = MakeUniformBufferElementDescriptor(1800, {9, 9, 2});
+    auto temp = MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2});
     ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
         &temp, &descriptors_for_double_10[1]));
   }
@@ -443,17 +485,17 @@
           context.get(), double_constant_ids[1]);
   ASSERT_EQ(1, descriptors_for_double_20.size());
   {
-    auto temp = MakeUniformBufferElementDescriptor(1900, {0, 0, 0, 0, 0});
+    auto temp = MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0});
     ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
         &temp, &descriptors_for_double_20[0]));
   }
 
   auto constant_1_id = fact_manager.GetConstantFromUniformDescriptor(
-      context.get(), MakeUniformBufferElementDescriptor(1800, {9, 9, 2}));
+      context.get(), MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2}));
   ASSERT_TRUE(constant_1_id);
 
   auto constant_2_id = fact_manager.GetConstantFromUniformDescriptor(
-      context.get(), MakeUniformBufferElementDescriptor(1900, {0, 0, 0, 0, 0}));
+      context.get(), MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}));
   ASSERT_TRUE(constant_2_id);
 
   ASSERT_EQ(double_constant_ids[0], constant_1_id);
@@ -505,9 +547,9 @@
   FactManager fact_manager;
 
   auto uniform_buffer_element_descriptor =
-      MakeUniformBufferElementDescriptor(12, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
 
-  // 12[0] = int(1)
+  // (0, 0, [0]) = int(1)
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
                             uniform_buffer_element_descriptor));
   auto constants =
@@ -569,12 +611,11 @@
   ASSERT_TRUE(IsValid(env, context.get()));
 
   FactManager fact_manager;
-
   auto uniform_buffer_element_descriptor_f =
-      MakeUniformBufferElementDescriptor(9, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
 
   auto uniform_buffer_element_descriptor_d =
-      MakeUniformBufferElementDescriptor(9, {1});
+      MakeUniformBufferElementDescriptor(0, 0, {1});
 
   if (std::numeric_limits<float>::has_infinity) {
     // f == +inf
@@ -626,6 +667,77 @@
   }
 }
 
+TEST(FactManagerTest, AmbiguousFact) {
+  //  This test came from the following GLSL:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(set = 0, binding = 0) uniform buf {
+  //   float f;
+  // };
+  //
+  // layout(set = 0, binding = 0) uniform buf2 {
+  //   float g;
+  // };
+  //
+  // void main() {
+  //
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "f"
+               OpName %9 ""
+               OpName %10 "buf2"
+               OpMemberName %10 0 "g"
+               OpName %12 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               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 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 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 =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  // The fact cannot be added because it is ambiguous: there are two uniforms
+  // with descriptor set 0 and binding 0.
+  ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {1},
+                             uniform_buffer_element_descriptor));
+}
+
 }  // 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
index 3cea325..89f006e 100644
--- a/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
+++ b/test/fuzz/fuzzer_pass_add_useful_constructs_test.cpp
@@ -180,30 +180,30 @@
 
   // buf.s.x == 200
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 200,
-                            MakeUniformBufferElementDescriptor(14, {0, 0})));
+                            MakeUniformBufferElementDescriptor(0, 0, {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})));
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 1})));
 
   // buf.s.z == 300
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 300,
-                            MakeUniformBufferElementDescriptor(14, {0, 2})));
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 2})));
 
   // buf.s.w == 400
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 400,
-                            MakeUniformBufferElementDescriptor(14, {0, 3})));
+                            MakeUniformBufferElementDescriptor(0, 0, {0, 3})));
 
   // buf.w[6] = 22
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 22,
-                            MakeUniformBufferElementDescriptor(14, {1, 6})));
+                            MakeUniformBufferElementDescriptor(0, 0, {1, 6})));
 
   // buf.w[8] = 23
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 23,
-                            MakeUniformBufferElementDescriptor(14, {1, 8})));
+                            MakeUniformBufferElementDescriptor(0, 0, {1, 8})));
 
   // Assert some things about the module that are not true prior to adding the
   // pass
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index bd753af..8b3775b 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -951,7 +951,7 @@
   {
     protobufs::FactConstantUniform resolution_x_eq_250;
     *resolution_x_eq_250.mutable_uniform_buffer_element_descriptor() =
-        MakeUniformBufferElementDescriptor(24, {0, 0});
+        MakeUniformBufferElementDescriptor(0, 0, {0, 0});
     *resolution_x_eq_250.mutable_constant_word()->Add() = 250;
     protobufs::Fact temp;
     *temp.mutable_constant_uniform_fact() = resolution_x_eq_250;
@@ -960,7 +960,7 @@
   {
     protobufs::FactConstantUniform resolution_y_eq_100;
     *resolution_y_eq_100.mutable_uniform_buffer_element_descriptor() =
-        MakeUniformBufferElementDescriptor(24, {0, 1});
+        MakeUniformBufferElementDescriptor(0, 0, {0, 1});
     *resolution_y_eq_100.mutable_constant_word()->Add() = 100;
     protobufs::Fact temp;
     *temp.mutable_constant_uniform_fact() = resolution_y_eq_100;
diff --git a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
index b3a0ccd..5a10173 100644
--- a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
+++ b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
@@ -104,11 +104,11 @@
 
   FactManager fact_manager;
   protobufs::UniformBufferElementDescriptor blockname_a =
-      MakeUniformBufferElementDescriptor(18, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_b =
-      MakeUniformBufferElementDescriptor(18, {1});
+      MakeUniformBufferElementDescriptor(0, 0, {1});
   protobufs::UniformBufferElementDescriptor blockname_c =
-      MakeUniformBufferElementDescriptor(18, {2});
+      MakeUniformBufferElementDescriptor(0, 0, {2});
 
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_a));
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_b));
@@ -157,9 +157,9 @@
   // The following transformations do not apply because the uniform descriptors
   // are not sensible.
   protobufs::UniformBufferElementDescriptor nonsense_uniform_descriptor1 =
-      MakeUniformBufferElementDescriptor(19, {0});
+      MakeUniformBufferElementDescriptor(1, 2, {0});
   protobufs::UniformBufferElementDescriptor nonsense_uniform_descriptor2 =
-      MakeUniformBufferElementDescriptor(18, {5});
+      MakeUniformBufferElementDescriptor(0, 0, {5});
   ASSERT_FALSE(transformation::IsApplicable(
       transformation::MakeTransformationReplaceConstantWithUniform(
           use_of_9_in_store, nonsense_uniform_descriptor1, 101, 102),
@@ -473,13 +473,13 @@
 
   FactManager fact_manager;
   protobufs::UniformBufferElementDescriptor blockname_1 =
-      MakeUniformBufferElementDescriptor(28, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_2 =
-      MakeUniformBufferElementDescriptor(28, {1, 1});
+      MakeUniformBufferElementDescriptor(0, 0, {1, 1});
   protobufs::UniformBufferElementDescriptor blockname_3 =
-      MakeUniformBufferElementDescriptor(28, {1, 0, 0});
+      MakeUniformBufferElementDescriptor(0, 0, {1, 0, 0});
   protobufs::UniformBufferElementDescriptor blockname_4 =
-      MakeUniformBufferElementDescriptor(28, {1, 0, 1, 0});
+      MakeUniformBufferElementDescriptor(0, 0, {1, 0, 1, 0});
 
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 1, blockname_1));
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 2, blockname_2));
@@ -712,7 +712,7 @@
 
   FactManager fact_manager;
   protobufs::UniformBufferElementDescriptor blockname_0 =
-      MakeUniformBufferElementDescriptor(12, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
 
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 0, blockname_0));
 
@@ -786,9 +786,9 @@
 
   FactManager fact_manager;
   protobufs::UniformBufferElementDescriptor blockname_0 =
-      MakeUniformBufferElementDescriptor(12, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_9 =
-      MakeUniformBufferElementDescriptor(12, {1});
+      MakeUniformBufferElementDescriptor(0, 0, {1});
 
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
 
@@ -859,7 +859,7 @@
 
   FactManager fact_manager;
   protobufs::UniformBufferElementDescriptor blockname_3 =
-      MakeUniformBufferElementDescriptor(12, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
 
   uint32_t float_data[1];
   float temp = 3.0;
@@ -946,9 +946,9 @@
 
   FactManager fact_manager;
   protobufs::UniformBufferElementDescriptor blockname_9 =
-      MakeUniformBufferElementDescriptor(14, {0});
+      MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_10 =
-      MakeUniformBufferElementDescriptor(14, {1});
+      MakeUniformBufferElementDescriptor(0, 0, {1});
 
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 9, blockname_9));
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), 10, blockname_10));
@@ -1172,42 +1172,42 @@
   memcpy(&float_vector_data, &float_vector_values, sizeof(float_vector_values));
 
   protobufs::UniformBufferElementDescriptor uniform_f_a_0 =
-      MakeUniformBufferElementDescriptor(65, {0, 0, 0});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 0});
   protobufs::UniformBufferElementDescriptor uniform_f_a_1 =
-      MakeUniformBufferElementDescriptor(65, {0, 0, 1});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 1});
   protobufs::UniformBufferElementDescriptor uniform_f_a_2 =
-      MakeUniformBufferElementDescriptor(65, {0, 0, 2});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 2});
   protobufs::UniformBufferElementDescriptor uniform_f_a_3 =
-      MakeUniformBufferElementDescriptor(65, {0, 0, 3});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 3});
   protobufs::UniformBufferElementDescriptor uniform_f_a_4 =
-      MakeUniformBufferElementDescriptor(65, {0, 0, 4});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 0, 4});
 
   protobufs::UniformBufferElementDescriptor uniform_f_b_x =
-      MakeUniformBufferElementDescriptor(65, {0, 1, 0});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 0});
   protobufs::UniformBufferElementDescriptor uniform_f_b_y =
-      MakeUniformBufferElementDescriptor(65, {0, 1, 1});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 1});
   protobufs::UniformBufferElementDescriptor uniform_f_b_z =
-      MakeUniformBufferElementDescriptor(65, {0, 1, 2});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 2});
   protobufs::UniformBufferElementDescriptor uniform_f_b_w =
-      MakeUniformBufferElementDescriptor(65, {0, 1, 3});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 1, 3});
 
   protobufs::UniformBufferElementDescriptor uniform_f_c_x =
-      MakeUniformBufferElementDescriptor(65, {0, 2, 0});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 2, 0});
   protobufs::UniformBufferElementDescriptor uniform_f_c_y =
-      MakeUniformBufferElementDescriptor(65, {0, 2, 1});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 2, 1});
   protobufs::UniformBufferElementDescriptor uniform_f_c_z =
-      MakeUniformBufferElementDescriptor(65, {0, 2, 2});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 2, 2});
 
   protobufs::UniformBufferElementDescriptor uniform_f_d =
-      MakeUniformBufferElementDescriptor(65, {0, 3});
+      MakeUniformBufferElementDescriptor(0, 0, {0, 3});
 
   protobufs::UniformBufferElementDescriptor uniform_g =
-      MakeUniformBufferElementDescriptor(65, {1});
+      MakeUniformBufferElementDescriptor(0, 0, {1});
 
   protobufs::UniformBufferElementDescriptor uniform_h_x =
-      MakeUniformBufferElementDescriptor(65, {2, 0});
+      MakeUniformBufferElementDescriptor(0, 0, {2, 0});
   protobufs::UniformBufferElementDescriptor uniform_h_y =
-      MakeUniformBufferElementDescriptor(65, {2, 1});
+      MakeUniformBufferElementDescriptor(0, 0, {2, 1});
 
   ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), float_array_data[0],
                             uniform_f_a_0));
diff --git a/test/fuzz/uniform_buffer_element_descriptor_test.cpp b/test/fuzz/uniform_buffer_element_descriptor_test.cpp
new file mode 100644
index 0000000..6c6d52a
--- /dev/null
+++ b/test/fuzz/uniform_buffer_element_descriptor_test.cpp
@@ -0,0 +1,84 @@
+// 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/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(UniformBufferElementDescriptorTest, TestEquality) {
+  // Test that equality works as expected for various buffer element
+  // descriptors.
+
+  protobufs::UniformBufferElementDescriptor descriptor1 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor2 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor3 =
+      MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor4 =
+      MakeUniformBufferElementDescriptor(1, 0, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor5 =
+      MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3});
+  protobufs::UniformBufferElementDescriptor descriptor6 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2, 4});
+  protobufs::UniformBufferElementDescriptor descriptor7 =
+      MakeUniformBufferElementDescriptor(0, 0, {1, 2});
+
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor1));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor2));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor3, &descriptor3));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor4, &descriptor4));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor5, &descriptor5));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor6, &descriptor6));
+  ASSERT_TRUE(
+      UniformBufferElementDescriptorEquals()(&descriptor7, &descriptor7));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor3));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor3, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor4));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor4, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor5));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor5, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor6));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor6, &descriptor1));
+
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor1, &descriptor7));
+  ASSERT_FALSE(
+      UniformBufferElementDescriptorEquals()(&descriptor7, &descriptor1));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools