Bläddra i källkod

Updated spirv-tools.

Бранимир Караџић 6 år sedan
förälder
incheckning
ab5cd6dc87
42 ändrade filer med 4750 tillägg och 566 borttagningar
  1. 33 1
      3rdparty/spirv-tools/README.md
  2. 1 1
      3rdparty/spirv-tools/include/generated/build-version.inc
  3. 2 0
      3rdparty/spirv-tools/source/fuzz/CMakeLists.txt
  4. 11 6
      3rdparty/spirv-tools/source/fuzz/equivalence_relation.h
  5. 494 43
      3rdparty/spirv-tools/source/fuzz/fact_manager.cpp
  6. 18 11
      3rdparty/spirv-tools/source/fuzz/fact_manager.h
  7. 1 1
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
  8. 1 1
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_useful_constructs.cpp
  9. 1 1
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
  10. 1 1
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
  11. 1 1
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
  12. 106 79
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
  13. 42 21
      3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp
  14. 15 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_util.h
  15. 3 23
      3rdparty/spirv-tools/source/fuzz/id_use_descriptor.cpp
  16. 23 1
      3rdparty/spirv-tools/source/fuzz/instruction_descriptor.cpp
  17. 4 0
      3rdparty/spirv-tools/source/fuzz/instruction_descriptor.h
  18. 28 9
      3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto
  19. 3 0
      3rdparty/spirv-tools/source/fuzz/transformation.cpp
  20. 2 0
      3rdparty/spirv-tools/source/fuzz/transformation_composite_extract.cpp
  21. 39 149
      3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.cpp
  22. 23 17
      3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.h
  23. 203 0
      3rdparty/spirv-tools/source/fuzz/transformation_vector_shuffle.cpp
  24. 82 0
      3rdparty/spirv-tools/source/fuzz/transformation_vector_shuffle.h
  25. 10 5
      3rdparty/spirv-tools/source/val/validate_misc.cpp
  26. 23 27
      3rdparty/spirv-tools/source/val/validate_scopes.cpp
  27. 3 0
      3rdparty/spirv-tools/source/val/validate_scopes.h
  28. 3 0
      3rdparty/spirv-tools/test/fuzz/CMakeLists.txt
  29. 1122 0
      3rdparty/spirv-tools/test/fuzz/data_synonym_transformation_test.cpp
  30. 22 0
      3rdparty/spirv-tools/test/fuzz/equivalence_relation_test.cpp
  31. 435 0
      3rdparty/spirv-tools/test/fuzz/fact_manager_test.cpp
  32. 116 95
      3rdparty/spirv-tools/test/fuzz/transformation_composite_construct_test.cpp
  33. 12 6
      3rdparty/spirv-tools/test/fuzz/transformation_composite_extract_test.cpp
  34. 22 21
      3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp
  35. 1213 0
      3rdparty/spirv-tools/test/fuzz/transformation_replace_id_with_synonym_test.cpp
  36. 532 0
      3rdparty/spirv-tools/test/fuzz/transformation_vector_shuffle_test.cpp
  37. 12 17
      3rdparty/spirv-tools/test/val/val_atomics_test.cpp
  38. 13 20
      3rdparty/spirv-tools/test/val/val_barriers_test.cpp
  39. 2 3
      3rdparty/spirv-tools/test/val/val_decoration_test.cpp
  40. 65 4
      3rdparty/spirv-tools/test/val/val_misc_test.cpp
  41. 1 1
      3rdparty/spirv-tools/utils/update_build_version.py
  42. 7 1
      3rdparty/spirv-tools/utils/vscode/src/grammar/grammar.go

+ 33 - 1
3rdparty/spirv-tools/README.md

@@ -33,7 +33,7 @@ headers, and XML registry.
 See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version.
 
 SPIRV-Tools project version numbers are of the form `v`*year*`.`*index* and with
-an optional `-dev` suffix to indicate work in progress.  For exampe, the
+an optional `-dev` suffix to indicate work in progress.  For example, the
 following versions are ordered from oldest to newest:
 
 * `v2016.0`
@@ -149,6 +149,24 @@ issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
 "Reducer:" as the start of its title.
 
 
+### Fuzzer
+
+*Note:* The fuzzer is still under development.
+
+The fuzzer applies semantics-preserving transformations to a SPIR-V binary
+module, to produce an equivalent module.  The original and transformed modules
+should produce essentially identical results when executed on identical inputs:
+their results should differ only due to floating-point round-off, if at all.
+Significant differences in results can pinpoint bugs in tools that process
+SPIR-V binaries, such as miscompilations.  This *metamorphic testing* approach
+is similar to the method used by the [GraphicsFuzz
+project](https://github.com/google/graphicsfuzz) for fuzzing of GLSL shaders.
+
+To suggest an additional capability for the fuzzer, [file an
+issue](https://github.com/KhronosGroup/SPIRV-Tools/issues]) with
+"Fuzzer:" as the start of its title.
+
+
 ### Extras
 
 * [Utility filters](#utility-filters)
@@ -324,6 +342,7 @@ Other compilers or later versions may work, but they are not tested.
 
 The following CMake options are supported:
 
+* `SPIRV_BUILD_FUZZER={ON|OFF}`, default `OFF` - Build the spirv-fuzz tool.
 * `SPIRV_COLOR_TERMINAL={ON|OFF}`, default `ON` - Enables color console output.
 * `SPIRV_SKIP_TESTS={ON|OFF}`, default `OFF`- Build only the library and
   the command line tools.  This will prevent the tests from being built.
@@ -500,6 +519,19 @@ This is a work in progress, with initially only shrinks a module in a few ways.
 
 Run `spirv-reduce --help` to see how to specify interestingness.
 
+### Fuzzer tool
+
+The fuzzer transforms a SPIR-V binary module into a semantically-equivalent
+SPIR-V binary module by applying transformations in a randomized fashion.
+
+This is a work in progress, with initially only a few semantics-preserving
+transformations.
+
+* `spirv-fuzz` - the standalone fuzzer
+  * `<spirv-dir>/tools/fuzz`
+
+Run `spirv-fuzz --help` for a detailed list of options.
+
 ### Control flow dumper tool
 
 The control flow dumper prints the control flow graph for a SPIR-V module as a

+ 1 - 1
3rdparty/spirv-tools/include/generated/build-version.inc

@@ -1 +1 @@
-"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-153-ge0d5544c"
+"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-164-g3e4abc9a"

+ 2 - 0
3rdparty/spirv-tools/source/fuzz/CMakeLists.txt

@@ -80,6 +80,7 @@ if(SPIRV_BUILD_FUZZER)
         transformation_set_memory_operands_mask.h
         transformation_set_selection_control.h
         transformation_split_block.h
+        transformation_vector_shuffle.h
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
@@ -132,6 +133,7 @@ if(SPIRV_BUILD_FUZZER)
         transformation_set_memory_operands_mask.cpp
         transformation_set_selection_control.cpp
         transformation_split_block.cpp
+        transformation_vector_shuffle.cpp
         uniform_buffer_element_descriptor.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
         )

+ 11 - 6
3rdparty/spirv-tools/source/fuzz/equivalence_relation.h

@@ -86,7 +86,7 @@ class EquivalenceRelation {
         // children.
         assert(pointer_to_value && "Representatives should never be null.");
         parent_[pointer_to_value] = pointer_to_value;
-        children_[pointer_to_value] = std::unordered_set<const T*>();
+        children_[pointer_to_value] = std::vector<const T*>();
       }
     }
 
@@ -108,7 +108,7 @@ class EquivalenceRelation {
     assert(representative2 && "Representatives should never be null.");
     if (representative1 != representative2) {
       parent_[representative1] = representative2;
-      children_[representative2].insert(representative1);
+      children_[representative2].push_back(representative1);
     }
   }
 
@@ -195,8 +195,13 @@ class EquivalenceRelation {
     while (parent_[current] != result) {
       const T* next = parent_[current];
       parent_[current] = result;
-      children_[result].insert(current);
-      children_[next].erase(current);
+      children_[result].push_back(current);
+      auto child_iterator =
+          std::find(children_[next].begin(), children_[next].end(), current);
+      assert(child_iterator != children_[next].end() &&
+             "'next' is the parent of 'current', so 'current' should be a "
+             "child of 'next'");
+      children_[next].erase(child_iterator);
       current = next;
     }
     return result;
@@ -216,9 +221,9 @@ class EquivalenceRelation {
   //
   // Mutable because the intuitively const method, 'Find', performs path
   // compression.
-  mutable std::unordered_map<const T*, std::unordered_set<const T*>> children_;
+  mutable std::unordered_map<const T*, std::vector<const T*>> children_;
 
-  // The values known to the equivalence relation are alloacated in
+  // The values known to the equivalence relation are allocated in
   // |owned_values_|, and |value_pool_| provides (via |PointerHashT| and
   // |PointerEqualsT|) a means for mapping a value of interest to a pointer
   // into an equivalent value in |owned_values_|.

+ 494 - 43
3rdparty/spirv-tools/source/fuzz/fact_manager.cpp

@@ -14,8 +14,9 @@
 
 #include "source/fuzz/fact_manager.h"
 
-#include <map>
 #include <sstream>
+#include <unordered_map>
+#include <unordered_set>
 
 #include "source/fuzz/equivalence_relation.h"
 #include "source/fuzz/fuzzer_util.h"
@@ -29,7 +30,7 @@ namespace {
 
 std::string ToString(const protobufs::Fact& fact) {
   assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact &&
-         "Right now this is the only fact.");
+         "Right now this is the only fact we know how to stringify.");
   std::stringstream stream;
   stream << "("
          << fact.constant_uniform_fact()
@@ -74,9 +75,10 @@ std::string ToString(const protobufs::Fact& fact) {
 //=======================
 // Constant uniform facts
 
-// The purpose of this struct is to group the fields and data used to represent
+// The purpose of this class is to group the fields and data used to represent
 // facts about uniform constants.
-struct FactManager::ConstantUniformFacts {
+class FactManager::ConstantUniformFacts {
+ public:
   // See method in FactManager which delegates to this method.
   bool AddFact(const protobufs::FactConstantUniform& fact,
                opt::IRContext* context);
@@ -99,6 +101,11 @@ struct FactManager::ConstantUniformFacts {
   // See method in FactManager which delegates to this method.
   std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
 
+  // See method in FactManager which delegates to this method.
+  const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+  GetConstantUniformFactsAndTypes() const;
+
+ private:
   // Returns true if and only if the words associated with
   // |constant_instruction| exactly match the words for the constant associated
   // with |constant_uniform_fact|.
@@ -124,7 +131,7 @@ struct FactManager::ConstantUniformFacts {
                                     uint32_t width) const;
 
   std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
-      facts_and_type_ids;
+      facts_and_type_ids_;
 };
 
 uint32_t FactManager::ConstantUniformFacts::GetConstantId(
@@ -163,7 +170,7 @@ FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType(
     opt::IRContext* ir_context, uint32_t type_id) const {
   std::vector<uint32_t> result;
   std::set<uint32_t> already_seen;
-  for (auto& fact_and_type_id : facts_and_type_ids) {
+  for (auto& fact_and_type_id : facts_and_type_ids_) {
     if (fact_and_type_id.second != type_id) {
       continue;
     }
@@ -186,7 +193,7 @@ FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant(
   assert(constant_inst->opcode() == SpvOpConstant &&
          "The given id must be that of a constant");
   auto type_id = constant_inst->type_id();
-  for (auto& fact_and_type_id : facts_and_type_ids) {
+  for (auto& fact_and_type_id : facts_and_type_ids_) {
     if (fact_and_type_id.second != type_id) {
       continue;
     }
@@ -202,7 +209,7 @@ uint32_t FactManager::ConstantUniformFacts::GetConstantFromUniformDescriptor(
     opt::IRContext* context,
     const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
   // Consider each fact.
-  for (auto& fact_and_type : facts_and_type_ids) {
+  for (auto& fact_and_type : facts_and_type_ids_) {
     // Check whether the uniform descriptor associated with the fact matches
     // |uniform_descriptor|.
     if (UniformBufferElementDescriptorEquals()(
@@ -219,7 +226,7 @@ std::vector<uint32_t>
 FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown()
     const {
   std::vector<uint32_t> result;
-  for (auto& fact_and_type : facts_and_type_ids) {
+  for (auto& fact_and_type : facts_and_type_ids_) {
     if (std::find(result.begin(), result.end(), fact_and_type.second) ==
         result.end()) {
       result.push_back(fact_and_type.second);
@@ -309,44 +316,485 @@ bool FactManager::ConstantUniformFacts::AddFact(
   if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
     return false;
   }
-  facts_and_type_ids.emplace_back(
+  facts_and_type_ids_.emplace_back(
       std::pair<protobufs::FactConstantUniform, uint32_t>(
           fact, final_element_type_id));
   return true;
 }
 
+const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+FactManager::ConstantUniformFacts::GetConstantUniformFactsAndTypes() const {
+  return facts_and_type_ids_;
+}
+
 // End of uniform constant facts
 //==============================
 
 //==============================
 // Data synonym facts
 
-// The purpose of this struct is to group the fields and data used to represent
+// The purpose of this class is to group the fields and data used to represent
 // facts about data synonyms.
-struct FactManager::DataSynonymFacts {
+class FactManager::DataSynonymFacts {
+ public:
   // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactDataSynonym& fact);
+  void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context);
 
   // See method in FactManager which delegates to this method.
-  bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
-                    const protobufs::DataDescriptor& data_descriptor2) const;
+  std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
+      const protobufs::DataDescriptor& data_descriptor,
+      opt::IRContext* context) const;
 
-  EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash,
-                      DataDescriptorEquals>
-      synonymous;
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown(
+      opt::IRContext* context) const;
+
+  // See method in FactManager which delegates to this method.
+  bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
+                    const protobufs::DataDescriptor& data_descriptor2,
+                    opt::IRContext* context) const;
+
+ private:
+  // Adds |fact| to the set of managed facts, and recurses into sub-components
+  // of the data descriptors referenced in |fact|, if they are composites, to
+  // record that their components are pairwise-synonymous.
+  void AddFactRecursive(const protobufs::FactDataSynonym& fact,
+                        opt::IRContext* context);
+
+  // Inspects all known facts and adds corollary facts; e.g. if we know that
+  // a.x == b.x and a.y == b.y, where a and b have vec2 type, we can record
+  // that a == b holds.
+  //
+  // This method is expensive, and is thus called on demand: rather than
+  // computing the closure of facts each time a data synonym fact is added, we
+  // compute the closure only when a data synonym fact is *queried*.
+  void ComputeClosureOfFacts(opt::IRContext* context) const;
+
+  // Returns true if and only if |dd1| and |dd2| are valid data descriptors
+  // whose associated data have the same type.
+  bool DataDescriptorsAreWellFormedAndComparable(
+      opt::IRContext* context, const protobufs::DataDescriptor& dd1,
+      const protobufs::DataDescriptor& dd2) const;
+
+  // The data descriptors that are known to be synonymous with one another are
+  // captured by this equivalence relation.
+  //
+  // This member is mutable in order to allow the closure of facts captured by
+  // the relation to be computed lazily when a question about data synonym
+  // facts is asked.
+  mutable EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash,
+                              DataDescriptorEquals>
+      synonymous_;
+
+  // When a new synonym fact is added, it may be possible to deduce further
+  // synonym facts by computing a closure of all known facts.  However, there is
+  // no point computing this closure until a question regarding synonym facts is
+  // actually asked: if several facts are added in succession with no questions
+  // asked in between, we can avoid computing fact closures multiple times.
+  //
+  // This boolean tracks whether a closure computation is required - i.e.,
+  // whether a new fact has been added since the last time such a computation
+  // was performed.
+  //
+  // It is mutable so faciliate having const methods, that provide answers to
+  // questions about data synonym facts, triggering closure computation on
+  // demand.
+  mutable bool closure_computation_required = false;
 };
 
 void FactManager::DataSynonymFacts::AddFact(
-    const protobufs::FactDataSynonym& fact) {
-  synonymous.MakeEquivalent(fact.data1(), fact.data2());
+    const protobufs::FactDataSynonym& fact, opt::IRContext* context) {
+  // Add the fact, including all facts relating sub-components of the data
+  // descriptors that are involved.
+  AddFactRecursive(fact, context);
+}
+
+void FactManager::DataSynonymFacts::AddFactRecursive(
+    const protobufs::FactDataSynonym& fact, opt::IRContext* context) {
+  assert(DataDescriptorsAreWellFormedAndComparable(context, fact.data1(),
+                                                   fact.data2()));
+
+  // Record that the data descriptors provided in the fact are equivalent.
+  synonymous_.MakeEquivalent(fact.data1(), fact.data2());
+  // As we have updated the equivalence relation, we might be able to deduce
+  // more facts by performing a closure computation, so we record that such a
+  // computation is required; it will be performed next time a method answering
+  // a data synonym fact-related question is invoked.
+  closure_computation_required = true;
+
+  // We now check whether this is a synonym about composite objects.  If it is,
+  // we can recursively add synonym facts about their associated sub-components.
+
+  // Get the type of the object referred to by the first data descriptor in the
+  // synonym fact.
+  uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices(
+      context,
+      context->get_def_use_mgr()->GetDef(fact.data1().object())->type_id(),
+      fact.data1().index());
+  auto type = context->get_type_mgr()->GetType(type_id);
+  auto type_instruction = context->get_def_use_mgr()->GetDef(type_id);
+  assert(type != nullptr &&
+         "Invalid data synonym fact: one side has an unknown type.");
+
+  // Check whether the type is composite, recording the number of elements
+  // associated with the composite if so.
+  uint32_t num_composite_elements;
+  if (type->AsArray()) {
+    num_composite_elements =
+        fuzzerutil::GetArraySize(*type_instruction, context);
+  } else if (type->AsMatrix()) {
+    num_composite_elements = type->AsMatrix()->element_count();
+  } else if (type->AsStruct()) {
+    num_composite_elements =
+        fuzzerutil::GetNumberOfStructMembers(*type_instruction);
+  } else if (type->AsVector()) {
+    num_composite_elements = type->AsVector()->element_count();
+  } else {
+    // The type is not a composite, so return.
+    return;
+  }
+
+  // If the fact has the form:
+  //   obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
+  // then for each composite index i, we add a fact of the form:
+  //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
+  for (uint32_t i = 0; i < num_composite_elements; i++) {
+    std::vector<uint32_t> extended_indices1 =
+        fuzzerutil::RepeatedFieldToVector(fact.data1().index());
+    extended_indices1.push_back(i);
+    std::vector<uint32_t> extended_indices2 =
+        fuzzerutil::RepeatedFieldToVector(fact.data2().index());
+    extended_indices2.push_back(i);
+    protobufs::FactDataSynonym extended_data_synonym_fact;
+    *extended_data_synonym_fact.mutable_data1() =
+        MakeDataDescriptor(fact.data1().object(), std::move(extended_indices1));
+    *extended_data_synonym_fact.mutable_data2() =
+        MakeDataDescriptor(fact.data2().object(), std::move(extended_indices2));
+    AddFactRecursive(extended_data_synonym_fact, context);
+  }
+}
+
+void FactManager::DataSynonymFacts::ComputeClosureOfFacts(
+    opt::IRContext* context) const {
+  // Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct
+  // data descriptors that describe objects of the same composite type, and that
+  // the composite type is comprised of k components.
+  //
+  // For example, if m is a mat4x4 and v a vec4, we might consider:
+  //   m[2]: describes the 2nd column of m, a vec4
+  //   v[]: describes all of v, a vec4
+  //
+  // Suppose that we know, for every 0 <= i < k, that the fact:
+  //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
+  // holds - i.e. that the children of the two data descriptors are synonymous.
+  //
+  // Then we can conclude that:
+  //   obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
+  // holds.
+  //
+  // For instance, if we have the facts:
+  //   m[2, 0] == v[0]
+  //   m[2, 1] == v[1]
+  //   m[2, 2] == v[2]
+  //   m[2, 3] == v[3]
+  // then we can conclude that:
+  //   m[2] == v.
+  //
+  // This method repeatedly searches the equivalence relation of data
+  // descriptors, deducing and adding such facts, until a pass over the
+  // relation leads to no further facts being deduced.
+
+  // The method relies on working with pairs of data descriptors, and in
+  // particular being able to hash and compare such pairs.
+
+  using DataDescriptorPair =
+      std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>;
+
+  struct DataDescriptorPairHash {
+    std::size_t operator()(const DataDescriptorPair& pair) const {
+      return DataDescriptorHash()(&pair.first) ^
+             DataDescriptorHash()(&pair.second);
+    }
+  };
+
+  struct DataDescriptorPairEquals {
+    bool operator()(const DataDescriptorPair& first,
+                    const DataDescriptorPair& second) const {
+      return DataDescriptorEquals()(&first.first, &second.first) &&
+             DataDescriptorEquals()(&first.second, &second.second);
+    }
+  };
+
+  // This map records, for a given pair of composite data descriptors of the
+  // same type, all the indices at which the data descriptors are known to be
+  // synonymous.  A pair is a key to this map only if we have observed that
+  // the pair are synonymous at *some* index, but not at *all* indices.
+  // Once we find that a pair of data descriptors are equivalent at all indices
+  // we record the fact that they are synonymous and remove them from the map.
+  //
+  // Using the m and v example from above, initially the pair (m[2], v) would
+  // not be a key to the map.  If we find that m[2, 2] == v[2] holds, we would
+  // add an entry:
+  //   (m[2], v) -> [false, false, true, false]
+  // to record that they are synonymous at index 2.  If we then find that
+  // m[2, 0] == v[0] holds, we would update this entry to:
+  //   (m[2], v) -> [true, false, true, false]
+  // If we then find that m[2, 3] == v[3] holds, we would update this entry to:
+  //   (m[2], v) -> [true, false, true, true]
+  // Finally, if we then find that m[2, 1] == v[1] holds, which would make the
+  // boolean vector true at every index, we would add the fact:
+  //   m[2] == v
+  // to the equivalence relation and remove (m[2], v) from the map.
+  std::unordered_map<DataDescriptorPair, std::vector<bool>,
+                     DataDescriptorPairHash, DataDescriptorPairEquals>
+      candidate_composite_synonyms;
+
+  // We keep looking for new facts until we perform a complete pass over the
+  // equivalence relation without finding any new facts.
+  while (closure_computation_required) {
+    // We have not found any new facts yet during this pass; we set this to
+    // 'true' if we do find a new fact.
+    closure_computation_required = false;
+
+    // Consider each class in the equivalence relation.
+    for (auto representative :
+         synonymous_.GetEquivalenceClassRepresentatives()) {
+      auto equivalence_class = synonymous_.GetEquivalenceClass(*representative);
+
+      // Consider every data descriptor in the equivalence class.
+      for (auto dd1_it = equivalence_class.begin();
+           dd1_it != equivalence_class.end(); ++dd1_it) {
+        // If this data descriptor has no indices then it does not have the form
+        // obj_1[a_1, ..., a_m, i], so move on.
+        auto dd1 = *dd1_it;
+        if (dd1->index_size() == 0) {
+          continue;
+        }
+
+        // Consider every other data descriptor later in the equivalence class
+        // (due to symmetry, there is no need to compare with previous data
+        // descriptors).
+        auto dd2_it = dd1_it;
+        for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) {
+          auto dd2 = *dd2_it;
+          // If this data descriptor has no indices then it does not have the
+          // form obj_2[b_1, ..., b_n, i], so move on.
+          if (dd2->index_size() == 0) {
+            continue;
+          }
+
+          // At this point we know that:
+          // - |dd1| has the form obj_1[a_1, ..., a_m, i]
+          // - |dd2| has the form obj_2[b_1, ..., b_n, j]
+          assert(dd1->index_size() > 0 && dd2->index_size() > 0 &&
+                 "Control should not reach here if either data descriptor has "
+                 "no indices.");
+
+          // We are only interested if i == j.
+          if (dd1->index(dd1->index_size() - 1) !=
+              dd2->index(dd2->index_size() - 1)) {
+            continue;
+          }
+
+          const uint32_t common_final_index = dd1->index(dd1->index_size() - 1);
+
+          // Make data descriptors |dd1_prefix| and |dd2_prefix| for
+          //   obj_1[a_1, ..., a_m]
+          // and
+          //   obj_2[b_1, ..., b_n]
+          // These are the two data descriptors we might be getting closer to
+          // deducing as being synonymous, due to knowing that they are
+          // synonymous when extended by a particular index.
+          protobufs::DataDescriptor dd1_prefix;
+          dd1_prefix.set_object(dd1->object());
+          for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1);
+               i++) {
+            dd1_prefix.add_index(dd1->index(i));
+          }
+          protobufs::DataDescriptor dd2_prefix;
+          dd2_prefix.set_object(dd2->object());
+          for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1);
+               i++) {
+            dd2_prefix.add_index(dd2->index(i));
+          }
+          assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) &&
+                 "By construction these prefixes should be different.");
+
+          // If we already know that these prefixes are synonymous, move on.
+          if (synonymous_.Exists(dd1_prefix) &&
+              synonymous_.Exists(dd2_prefix) &&
+              synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) {
+            continue;
+          }
+
+          // Get the type of obj_1
+          auto dd1_root_type_id =
+              context->get_def_use_mgr()->GetDef(dd1->object())->type_id();
+          // Use this type, together with a_1, ..., a_m, to get the type of
+          // obj_1[a_1, ..., a_m].
+          auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
+              context, dd1_root_type_id, dd1_prefix.index());
+
+          // Similarly, get the type of obj_2 and use it to get the type of
+          // obj_2[b_1, ..., b_n].
+          auto dd2_root_type_id =
+              context->get_def_use_mgr()->GetDef(dd2->object())->type_id();
+          auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
+              context, dd2_root_type_id, dd2_prefix.index());
+
+          // If the types of dd1_prefix and dd2_prefix are not the same, they
+          // cannot be synonymous.
+          if (dd1_prefix_type != dd2_prefix_type) {
+            continue;
+          }
+
+          // At this point, we know we have synonymous data descriptors of the
+          // form:
+          //   obj_1[a_1, ..., a_m, i]
+          //   obj_2[b_1, ..., b_n, i]
+          // with the same last_index i, such that:
+          //   obj_1[a_1, ..., a_m]
+          // and
+          //   obj_2[b_1, ..., b_n]
+          // have the same type.
+
+          // Work out how many components there are in the (common) commposite
+          // type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n].
+          // This depends on whether the composite type is array, matrix, struct
+          // or vector.
+          uint32_t num_components_in_composite;
+          auto composite_type =
+              context->get_type_mgr()->GetType(dd1_prefix_type);
+          auto composite_type_instruction =
+              context->get_def_use_mgr()->GetDef(dd1_prefix_type);
+          if (composite_type->AsArray()) {
+            num_components_in_composite =
+                fuzzerutil::GetArraySize(*composite_type_instruction, context);
+            if (num_components_in_composite == 0) {
+              // This indicates that the array has an unknown size, in which
+              // case we cannot be sure we have matched all of its elements with
+              // synonymous elements of another array.
+              continue;
+            }
+          } else if (composite_type->AsMatrix()) {
+            num_components_in_composite =
+                composite_type->AsMatrix()->element_count();
+          } else if (composite_type->AsStruct()) {
+            num_components_in_composite = fuzzerutil::GetNumberOfStructMembers(
+                *composite_type_instruction);
+          } else {
+            assert(composite_type->AsVector());
+            num_components_in_composite =
+                composite_type->AsVector()->element_count();
+          }
+
+          // We are one step closer to being able to say that |dd1_prefix| and
+          // |dd2_prefix| are synonymous.
+          DataDescriptorPair candidate_composite_synonym(dd1_prefix,
+                                                         dd2_prefix);
+
+          // We look up what we already know about this pair.
+          auto existing_entry =
+              candidate_composite_synonyms.find(candidate_composite_synonym);
+
+          if (existing_entry == candidate_composite_synonyms.end()) {
+            // If this is the first time we have seen the pair, we make a vector
+            // of size |num_components_in_composite| that is 'true' at the
+            // common final index associated with |dd1| and |dd2|, and 'false'
+            // everywhere else, and register this vector as being associated
+            // with the pair.
+            std::vector<bool> entry;
+            for (uint32_t i = 0; i < num_components_in_composite; i++) {
+              entry.push_back(i == common_final_index);
+            }
+            candidate_composite_synonyms[candidate_composite_synonym] = entry;
+            existing_entry =
+                candidate_composite_synonyms.find(candidate_composite_synonym);
+          } else {
+            // We have seen this pair of data descriptors before, and we now
+            // know that they are synonymous at one further index, so we
+            // update the entry to record that.
+            existing_entry->second[common_final_index] = true;
+          }
+          assert(existing_entry != candidate_composite_synonyms.end());
+
+          // Check whether |dd1_prefix| and |dd2_prefix| are now known to match
+          // at every sub-component.
+          bool all_components_match = true;
+          for (uint32_t i = 0; i < num_components_in_composite; i++) {
+            if (!existing_entry->second[i]) {
+              all_components_match = false;
+              break;
+            }
+          }
+          if (all_components_match) {
+            // The two prefixes match on all sub-components, so we know that
+            // they are synonymous.  We add this fact *non-recursively*, as we
+            // have deduced that |dd1_prefix| and |dd2_prefix| are synonymous
+            // by observing that all their sub-components are already
+            // synonymous.
+            assert(DataDescriptorsAreWellFormedAndComparable(
+                context, dd1_prefix, dd2_prefix));
+            synonymous_.MakeEquivalent(dd1_prefix, dd2_prefix);
+            // As we have added a new synonym fact, we might benefit from doing
+            // another pass over the equivalence relation.
+            closure_computation_required = true;
+            // Now that we know this pair of data descriptors are synonymous,
+            // there is no point recording how close they are to being
+            // synonymous.
+            candidate_composite_synonyms.erase(candidate_composite_synonym);
+          }
+        }
+      }
+    }
+  }
+}
+
+bool FactManager::DataSynonymFacts::DataDescriptorsAreWellFormedAndComparable(
+    opt::IRContext* context, const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) const {
+  auto end_type_1 = fuzzerutil::WalkCompositeTypeIndices(
+      context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
+      dd1.index());
+  auto end_type_2 = fuzzerutil::WalkCompositeTypeIndices(
+      context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(),
+      dd2.index());
+  return end_type_1 && end_type_1 == end_type_2;
+}
+
+std::vector<const protobufs::DataDescriptor*>
+FactManager::DataSynonymFacts::GetSynonymsForDataDescriptor(
+    const protobufs::DataDescriptor& data_descriptor,
+    opt::IRContext* context) const {
+  ComputeClosureOfFacts(context);
+  if (synonymous_.Exists(data_descriptor)) {
+    return synonymous_.GetEquivalenceClass(data_descriptor);
+  }
+  return std::vector<const protobufs::DataDescriptor*>();
+}
+
+std::vector<uint32_t>
+FactManager::DataSynonymFacts ::GetIdsForWhichSynonymsAreKnown(
+    opt::IRContext* context) const {
+  ComputeClosureOfFacts(context);
+  std::vector<uint32_t> result;
+  for (auto& data_descriptor : synonymous_.GetAllKnownValues()) {
+    if (data_descriptor->index().empty()) {
+      result.push_back(data_descriptor->object());
+    }
+  }
+  return result;
 }
 
 bool FactManager::DataSynonymFacts::IsSynonymous(
     const protobufs::DataDescriptor& data_descriptor1,
-    const protobufs::DataDescriptor& data_descriptor2) const {
-  return synonymous.Exists(data_descriptor1) &&
-         synonymous.Exists(data_descriptor2) &&
-         synonymous.IsEquivalent(data_descriptor1, data_descriptor2);
+    const protobufs::DataDescriptor& data_descriptor2,
+    opt::IRContext* context) const {
+  const_cast<FactManager::DataSynonymFacts*>(this)->ComputeClosureOfFacts(
+      context);
+  return synonymous_.Exists(data_descriptor1) &&
+         synonymous_.Exists(data_descriptor2) &&
+         synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
 }
 
 // End of data synonym facts
@@ -377,7 +825,7 @@ bool FactManager::AddFact(const fuzz::protobufs::Fact& fact,
       return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
                                               context);
     case protobufs::Fact::kDataSynonymFact:
-      data_synonym_facts_->AddFact(fact.data_synonym_fact());
+      data_synonym_facts_->AddFact(fact.data_synonym_fact(), context);
       return true;
     default:
       assert(false && "Unknown fact type.");
@@ -387,11 +835,11 @@ bool FactManager::AddFact(const fuzz::protobufs::Fact& fact,
 
 void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1,
                                      const protobufs::DataDescriptor& data2,
-                                     opt::IRContext* /*unused*/) {
+                                     opt::IRContext* context) {
   protobufs::FactDataSynonym fact;
   *fact.mutable_data1() = data1;
   *fact.mutable_data2() = data2;
-  data_synonym_facts_->AddFact(fact);
+  data_synonym_facts_->AddFact(fact, context);
 }
 
 std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
@@ -421,31 +869,34 @@ std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
 
 const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
 FactManager::GetConstantUniformFactsAndTypes() const {
-  return uniform_constant_facts_->facts_and_type_ids;
+  return uniform_constant_facts_->GetConstantUniformFactsAndTypes();
 }
 
-std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
-  std::vector<uint32_t> result;
-  for (auto& data_descriptor :
-       data_synonym_facts_->synonymous.GetAllKnownValues()) {
-    if (data_descriptor->index().empty()) {
-      result.push_back(data_descriptor->object());
-    }
-  }
-  return result;
+std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown(
+    opt::IRContext* context) const {
+  return data_synonym_facts_->GetIdsForWhichSynonymsAreKnown(context);
+}
+
+std::vector<const protobufs::DataDescriptor*>
+FactManager::GetSynonymsForDataDescriptor(
+    const protobufs::DataDescriptor& data_descriptor,
+    opt::IRContext* context) const {
+  return data_synonym_facts_->GetSynonymsForDataDescriptor(data_descriptor,
+                                                           context);
 }
 
 std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
-    uint32_t id) const {
-  return data_synonym_facts_->synonymous.GetEquivalenceClass(
-      MakeDataDescriptor(id, {}));
+    uint32_t id, opt::IRContext* context) const {
+  return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {}), context);
 }
 
 bool FactManager::IsSynonymous(
     const protobufs::DataDescriptor& data_descriptor1,
-    const protobufs::DataDescriptor& data_descriptor2) const {
-  return data_synonym_facts_->IsSynonymous(data_descriptor1, data_descriptor2);
-};
+    const protobufs::DataDescriptor& data_descriptor2,
+    opt::IRContext* context) const {
+  return data_synonym_facts_->IsSynonymous(data_descriptor1, data_descriptor2,
+                                           context);
+}
 
 }  // namespace fuzz
 }  // namespace spvtools

+ 18 - 11
3rdparty/spirv-tools/source/fuzz/fact_manager.h

@@ -105,34 +105,41 @@ class FactManager {
   //==============================
   // Querying facts about id synonyms
 
-  // Returns every id for which a fact of the form "this id is synonymous
-  // with this piece of data" is known.
-  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
+  // Returns every id for which a fact of the form "this id is synonymous with
+  // this piece of data" is known.
+  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown(
+      opt::IRContext* context) const;
 
   // Returns the equivalence class of all known synonyms of |id|, or an empty
   // set if no synonyms are known.
   std::vector<const protobufs::DataDescriptor*> GetSynonymsForId(
-      uint32_t id) const;
+      uint32_t id, opt::IRContext* context) const;
 
-  // Return true if and ony if |data_descriptor1| and |data_descriptor2| are
+  // Returns the equivalence class of all known synonyms of |data_descriptor|,
+  // or empty if no synonyms are known.
+  std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
+      const protobufs::DataDescriptor& data_descriptor,
+      opt::IRContext* context) const;
+
+  // Returns true if and ony if |data_descriptor1| and |data_descriptor2| are
   // known to be synonymous.
   bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
-                    const protobufs::DataDescriptor& data_descriptor2) const;
+                    const protobufs::DataDescriptor& data_descriptor2,
+                    opt::IRContext* context) const;
 
   // End of id synonym facts
   //==============================
 
  private:
   // For each distinct kind of fact to be managed, we use a separate opaque
-  // struct type.
+  // class type.
 
-  struct ConstantUniformFacts;  // Opaque class for management of
-                                // constant uniform facts.
+  class ConstantUniformFacts;  // Opaque class for management of
+                               // constant uniform facts.
   std::unique_ptr<ConstantUniformFacts>
       uniform_constant_facts_;  // Unique pointer to internal data.
 
-  struct DataSynonymFacts;  // Opaque class for management of data synonym
-                            // facts.
+  class DataSynonymFacts;  // Opaque class for management of data synonym facts.
   std::unique_ptr<DataSynonymFacts>
       data_synonym_facts_;  // Unique pointer to internal data.
 };

+ 1 - 1
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp

@@ -23,7 +23,7 @@ FuzzerPassAddNoContractionDecorations::FuzzerPassAddNoContractionDecorations(
     opt::IRContext* ir_context, FactManager* fact_manager,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
 
 FuzzerPassAddNoContractionDecorations::
     ~FuzzerPassAddNoContractionDecorations() = default;

+ 1 - 1
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_useful_constructs.cpp

@@ -28,7 +28,7 @@ FuzzerPassAddUsefulConstructs::FuzzerPassAddUsefulConstructs(
     opt::IRContext* ir_context, FactManager* fact_manager,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
 
 FuzzerPassAddUsefulConstructs::~FuzzerPassAddUsefulConstructs() = default;
 

+ 1 - 1
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_function_controls.cpp

@@ -23,7 +23,7 @@ FuzzerPassAdjustFunctionControls::FuzzerPassAdjustFunctionControls(
     opt::IRContext* ir_context, FactManager* fact_manager,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
 
 FuzzerPassAdjustFunctionControls::~FuzzerPassAdjustFunctionControls() = default;
 

+ 1 - 1
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp

@@ -23,7 +23,7 @@ FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
     opt::IRContext* ir_context, FactManager* fact_manager,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
 
 FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
 

+ 1 - 1
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp

@@ -23,7 +23,7 @@ FuzzerPassAdjustSelectionControls::FuzzerPassAdjustSelectionControls(
     opt::IRContext* ir_context, FactManager* fact_manager,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations)
-    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations){};
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
 
 FuzzerPassAdjustSelectionControls::~FuzzerPassAdjustSelectionControls() =
     default;

+ 106 - 79
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp

@@ -14,7 +14,11 @@
 
 #include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
 
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_composite_extract.h"
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
 
 namespace spvtools {
@@ -29,90 +33,113 @@ FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
 FuzzerPassApplyIdSynonyms::~FuzzerPassApplyIdSynonyms() = default;
 
 void FuzzerPassApplyIdSynonyms::Apply() {
-  std::vector<TransformationReplaceIdWithSynonym> transformations_to_apply;
-
   for (auto id_with_known_synonyms :
-       GetFactManager()->GetIdsForWhichSynonymsAreKnown()) {
+       GetFactManager()->GetIdsForWhichSynonymsAreKnown(GetIRContext())) {
+    // Gather up all uses of |id_with_known_synonym|, and then subsequently
+    // iterate over these uses.  We use this separation because, when
+    // considering a given use, we might apply a transformation that will
+    // invalidate the def-use manager.
+    std::vector<std::pair<opt::Instruction*, uint32_t>> uses;
     GetIRContext()->get_def_use_mgr()->ForEachUse(
         id_with_known_synonyms,
-        [this, id_with_known_synonyms, &transformations_to_apply](
-            opt::Instruction* use_inst, uint32_t use_index) -> void {
-          auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
-          // The use might not be in a block; e.g. it could be a decoration.
-          if (!block_containing_use) {
-            return;
-          }
-          if (!GetFuzzerContext()->ChoosePercentage(
-                  GetFuzzerContext()->GetChanceOfReplacingIdWithSynonym())) {
-            return;
-          }
-
-          // |use_index| is the absolute index of the operand.  We require
-          // the index of the operand restricted to input operands only, so
-          // we subtract the number of non-input operands from |use_index|.
-          uint32_t use_in_operand_index =
-              use_index - use_inst->NumOperands() + use_inst->NumInOperands();
-
-          std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
-          for (auto& data_descriptor :
-               GetFactManager()->GetSynonymsForId(id_with_known_synonyms)) {
-            synonyms_to_try.push_back(data_descriptor);
-          }
-          while (!synonyms_to_try.empty()) {
-            auto synonym_index =
-                GetFuzzerContext()->RandomIndex(synonyms_to_try);
-            auto synonym_to_try = synonyms_to_try[synonym_index];
-            synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index);
-
-            if (!TransformationReplaceIdWithSynonym::
-                    ReplacingUseWithSynonymIsOk(GetIRContext(), use_inst,
-                                                use_in_operand_index,
-                                                *synonym_to_try)) {
-              continue;
-            }
-
-            // At present, we generate direct id synonyms (through
-            // OpCopyObject), which require no indices, and id synonyms that
-            // require a single index (through OpCompositeConstruct).
-            assert(synonym_to_try->index_size() <= 1);
-
-            // If an index is required, then we need to extract an element
-            // from a composite (e.g. through OpCompositeExtract), and this
-            // requires a fresh result id.
-            auto fresh_id_for_temporary =
-                synonym_to_try->index().empty()
-                    ? 0
-                    : GetFuzzerContext()->GetFreshId();
-
-            TransformationReplaceIdWithSynonym replace_id_transformation(
-                MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
-                                           use_in_operand_index),
-                *synonym_to_try, fresh_id_for_temporary);
-
-            // The transformation should be applicable by construction.
-            assert(replace_id_transformation.IsApplicable(GetIRContext(),
-                                                          *GetFactManager()));
-            // We cannot actually apply the transformation here, as this would
-            // change the analysis results that are being depended on for usage
-            // iteration.  We instead store them up and apply them at the end
-            // of the method.
-            transformations_to_apply.push_back(replace_id_transformation);
-            break;
-          }
+        [&uses](opt::Instruction* use_inst, uint32_t use_index) -> void {
+          uses.emplace_back(
+              std::pair<opt::Instruction*, uint32_t>(use_inst, use_index));
         });
-  }
 
-  for (auto& replace_id_transformation : transformations_to_apply) {
-    // Even though replacing id uses with synonyms may lead to new instructions
-    // (to compute indices into composites), as these instructions will generate
-    // ids, their presence should not affect the id use descriptors that were
-    // computed during the creation of transformations. Thus transformations
-    // should not disable one another.
-    assert(replace_id_transformation.IsApplicable(GetIRContext(),
-                                                  *GetFactManager()));
-    replace_id_transformation.Apply(GetIRContext(), GetFactManager());
-    *GetTransformations()->add_transformation() =
-        replace_id_transformation.ToMessage();
+    for (auto& use : uses) {
+      auto use_inst = use.first;
+      auto use_index = use.second;
+      auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
+      // The use might not be in a block; e.g. it could be a decoration.
+      if (!block_containing_use) {
+        continue;
+      }
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfReplacingIdWithSynonym())) {
+        continue;
+      }
+      // |use_index| is the absolute index of the operand.  We require
+      // the index of the operand restricted to input operands only, so
+      // we subtract the number of non-input operands from |use_index|.
+      uint32_t use_in_operand_index =
+          use_index - use_inst->NumOperands() + use_inst->NumInOperands();
+      if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
+              GetIRContext(), use_inst, use_in_operand_index)) {
+        continue;
+      }
+
+      std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
+      for (auto& data_descriptor : GetFactManager()->GetSynonymsForId(
+               id_with_known_synonyms, GetIRContext())) {
+        protobufs::DataDescriptor descriptor_for_this_id =
+            MakeDataDescriptor(id_with_known_synonyms, {});
+        if (DataDescriptorEquals()(data_descriptor, &descriptor_for_this_id)) {
+          // Exclude the fact that the id is synonymous with itself.
+          continue;
+        }
+        synonyms_to_try.push_back(data_descriptor);
+      }
+      while (!synonyms_to_try.empty()) {
+        auto synonym_index = GetFuzzerContext()->RandomIndex(synonyms_to_try);
+        auto synonym_to_try = synonyms_to_try[synonym_index];
+        synonyms_to_try.erase(synonyms_to_try.begin() + synonym_index);
+
+        if (synonym_to_try->index_size() > 0 &&
+            use_inst->opcode() == SpvOpPhi) {
+          // We are trying to replace an operand to an OpPhi.  This means
+          // we cannot use a composite synonym, because that requires
+          // extracting a component from a composite and we cannot insert
+          // an extract instruction before an OpPhi.
+          //
+          // TODO(afd): We could consider inserting the extract instruction
+          //  into the relevant parent block of the OpPhi.
+          continue;
+        }
+
+        if (!TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse(
+                GetIRContext(), use_inst, use_in_operand_index,
+                synonym_to_try->object())) {
+          continue;
+        }
+
+        // We either replace the use with an id known to be synonymous, or
+        // an id that will hold the result of extracting a synonym from a
+        // composite.
+        uint32_t id_with_which_to_replace_use;
+        if (synonym_to_try->index_size() == 0) {
+          id_with_which_to_replace_use = synonym_to_try->object();
+        } else {
+          id_with_which_to_replace_use = GetFuzzerContext()->GetFreshId();
+          protobufs::InstructionDescriptor instruction_to_insert_before =
+              MakeInstructionDescriptor(GetIRContext(), use_inst);
+          TransformationCompositeExtract composite_extract_transformation(
+              instruction_to_insert_before, id_with_which_to_replace_use,
+              synonym_to_try->object(),
+              fuzzerutil::RepeatedFieldToVector(synonym_to_try->index()));
+          assert(composite_extract_transformation.IsApplicable(
+                     GetIRContext(), *GetFactManager()) &&
+                 "Transformation should be applicable by construction.");
+          composite_extract_transformation.Apply(GetIRContext(),
+                                                 GetFactManager());
+          *GetTransformations()->add_transformation() =
+              composite_extract_transformation.ToMessage();
+        }
+
+        TransformationReplaceIdWithSynonym replace_id_transformation(
+            MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
+                                       use_in_operand_index),
+            id_with_which_to_replace_use);
+
+        // The transformation should be applicable by construction.
+        assert(replace_id_transformation.IsApplicable(GetIRContext(),
+                                                      *GetFactManager()));
+        replace_id_transformation.Apply(GetIRContext(), GetFactManager());
+        *GetTransformations()->add_transformation() =
+            replace_id_transformation.ToMessage();
+        break;
+      }
+    }
   }
 }
 

+ 42 - 21
3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp

@@ -330,6 +330,15 @@ bool IsCompositeType(const opt::analysis::Type* type) {
                   type->AsVector());
 }
 
+std::vector<uint32_t> RepeatedFieldToVector(
+    const google::protobuf::RepeatedField<uint32_t>& repeated_field) {
+  std::vector<uint32_t> result;
+  for (auto i : repeated_field) {
+    result.push_back(i);
+  }
+  return result;
+}
+
 uint32_t WalkCompositeTypeIndices(
     opt::IRContext* context, uint32_t base_object_type_id,
     const google::protobuf::RepeatedField<google::protobuf::uint32>& indices) {
@@ -338,39 +347,31 @@ uint32_t WalkCompositeTypeIndices(
     auto should_be_composite_type =
         context->get_def_use_mgr()->GetDef(sub_object_type_id);
     assert(should_be_composite_type && "The type should exist.");
-    if (SpvOpTypeStruct == should_be_composite_type->opcode()) {
-      if (index >= should_be_composite_type->NumInOperands()) {
+    if (SpvOpTypeArray == should_be_composite_type->opcode()) {
+      auto array_length = GetArraySize(*should_be_composite_type, context);
+      if (array_length == 0 || index >= array_length) {
         return 0;
       }
-      sub_object_type_id =
-          should_be_composite_type->GetSingleWordInOperand(index);
-    } else if (SpvOpTypeArray == should_be_composite_type->opcode()) {
-      auto array_length_constant =
-          context->get_constant_mgr()
-              ->GetConstantFromInst(context->get_def_use_mgr()->GetDef(
-                  should_be_composite_type->GetSingleWordInOperand(1)))
-              ->AsIntConstant();
-      if (array_length_constant->words().size() != 1) {
+      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
+    } else if (SpvOpTypeMatrix == should_be_composite_type->opcode()) {
+      auto matrix_column_count =
+          should_be_composite_type->GetSingleWordInOperand(1);
+      if (index >= matrix_column_count) {
         return 0;
       }
-      auto array_length = array_length_constant->GetU32();
-      if (index >= array_length) {
+      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
+    } else if (SpvOpTypeStruct == should_be_composite_type->opcode()) {
+      if (index >= GetNumberOfStructMembers(*should_be_composite_type)) {
         return 0;
       }
-      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
+      sub_object_type_id =
+          should_be_composite_type->GetSingleWordInOperand(index);
     } else if (SpvOpTypeVector == should_be_composite_type->opcode()) {
       auto vector_length = should_be_composite_type->GetSingleWordInOperand(1);
       if (index >= vector_length) {
         return 0;
       }
       sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
-    } else if (SpvOpTypeMatrix == should_be_composite_type->opcode()) {
-      auto matrix_column_count =
-          should_be_composite_type->GetSingleWordInOperand(1);
-      if (index >= matrix_column_count) {
-        return 0;
-      }
-      sub_object_type_id = should_be_composite_type->GetSingleWordInOperand(0);
     } else {
       return 0;
     }
@@ -378,6 +379,26 @@ uint32_t WalkCompositeTypeIndices(
   return sub_object_type_id;
 }
 
+uint32_t GetNumberOfStructMembers(
+    const opt::Instruction& struct_type_instruction) {
+  assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
+         "An OpTypeStruct instruction is required here.");
+  return struct_type_instruction.NumInOperands();
+}
+
+uint32_t GetArraySize(const opt::Instruction& array_type_instruction,
+                      opt::IRContext* context) {
+  auto array_length_constant =
+      context->get_constant_mgr()
+          ->GetConstantFromInst(context->get_def_use_mgr()->GetDef(
+              array_type_instruction.GetSingleWordInOperand(1)))
+          ->AsIntConstant();
+  if (array_length_constant->words().size() != 1) {
+    return 0;
+  }
+  return array_length_constant->GetU32();
+}
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz

+ 15 - 0
3rdparty/spirv-tools/source/fuzz/fuzzer_util.h

@@ -94,6 +94,10 @@ bool CanMakeSynonymOf(opt::IRContext* ir_context, opt::Instruction* inst);
 // struct or vector.
 bool IsCompositeType(const opt::analysis::Type* type);
 
+// Returns a vector containing the same elements as |repeated_field|.
+std::vector<uint32_t> RepeatedFieldToVector(
+    const google::protobuf::RepeatedField<uint32_t>& repeated_field);
+
 // Given a type id, |base_object_type_id|, checks that the given sequence of
 // |indices| is suitable for indexing into this type.  Returns the id of the
 // type of the final sub-object reached via the indices if they are valid, and
@@ -102,6 +106,17 @@ uint32_t WalkCompositeTypeIndices(
     opt::IRContext* context, uint32_t base_object_type_id,
     const google::protobuf::RepeatedField<google::protobuf::uint32>& indices);
 
+// Returns the number of members associated with |struct_type_instruction|,
+// which must be an OpStructType instruction.
+uint32_t GetNumberOfStructMembers(
+    const opt::Instruction& struct_type_instruction);
+
+// Returns the constant size of the array associated with
+// |array_type_instruction|, which must be an OpArrayType instruction. Returns
+// 0 if there is not a static size.
+uint32_t GetArraySize(const opt::Instruction& array_type_instruction,
+                      opt::IRContext* context);
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz

+ 3 - 23
3rdparty/spirv-tools/source/fuzz/id_use_descriptor.cpp

@@ -53,29 +53,9 @@ protobufs::IdUseDescriptor MakeIdUseDescriptorFromUse(
     uint32_t in_operand_index) {
   auto in_operand = inst->GetInOperand(in_operand_index);
   assert(in_operand.type == SPV_OPERAND_TYPE_ID);
-  auto id_of_interest = in_operand.words[0];
-
-  auto block = context->get_instr_block(inst);
-  uint32_t base_instruction_result_id = block->id();
-  uint32_t num_opcodes_to_ignore = 0;
-  for (auto& inst_in_block : *block) {
-    if (inst_in_block.HasResultId()) {
-      base_instruction_result_id = inst_in_block.result_id();
-      num_opcodes_to_ignore = 0;
-    }
-    if (&inst_in_block == inst) {
-      return MakeIdUseDescriptor(
-          id_of_interest,
-          MakeInstructionDescriptor(base_instruction_result_id, inst->opcode(),
-                                    num_opcodes_to_ignore),
-          in_operand_index);
-    }
-    if (inst_in_block.opcode() == inst->opcode()) {
-      num_opcodes_to_ignore++;
-    }
-  }
-  assert(false && "No matching instruction was found.");
-  return protobufs::IdUseDescriptor();
+  return MakeIdUseDescriptor(in_operand.words[0],
+                             MakeInstructionDescriptor(context, inst),
+                             in_operand_index);
 }
 
 }  // namespace fuzz

+ 23 - 1
3rdparty/spirv-tools/source/fuzz/instruction_descriptor.cpp

@@ -72,7 +72,7 @@ protobufs::InstructionDescriptor MakeInstructionDescriptor(
   const SpvOp opcode =
       inst_it->opcode();    // The opcode of the instruction being described.
   uint32_t skip_count = 0;  // The number of these opcodes we have skipped when
-                            // searching backwards.
+  // searching backwards.
 
   // Consider instructions in the block in reverse order, starting from
   // |inst_it|.
@@ -101,5 +101,27 @@ protobufs::InstructionDescriptor MakeInstructionDescriptor(
   return MakeInstructionDescriptor(block.id(), opcode, skip_count);
 }
 
+protobufs::InstructionDescriptor MakeInstructionDescriptor(
+    opt::IRContext* context, opt::Instruction* inst) {
+  auto block = context->get_instr_block(inst);
+  uint32_t base_instruction_result_id = block->id();
+  uint32_t num_opcodes_to_ignore = 0;
+  for (auto& inst_in_block : *block) {
+    if (inst_in_block.HasResultId()) {
+      base_instruction_result_id = inst_in_block.result_id();
+      num_opcodes_to_ignore = 0;
+    }
+    if (&inst_in_block == inst) {
+      return MakeInstructionDescriptor(base_instruction_result_id,
+                                       inst->opcode(), num_opcodes_to_ignore);
+    }
+    if (inst_in_block.opcode() == inst->opcode()) {
+      num_opcodes_to_ignore++;
+    }
+  }
+  assert(false && "No matching instruction was found.");
+  return protobufs::InstructionDescriptor();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools

+ 4 - 0
3rdparty/spirv-tools/source/fuzz/instruction_descriptor.h

@@ -43,6 +43,10 @@ protobufs::InstructionDescriptor MakeInstructionDescriptor(
     const opt::BasicBlock& block,
     const opt::BasicBlock::const_iterator& inst_it);
 
+// Returns an InstructionDescriptor that describes the given instruction |inst|.
+protobufs::InstructionDescriptor MakeInstructionDescriptor(
+    opt::IRContext* context, opt::Instruction* inst);
+
 }  // namespace fuzz
 }  // namespace spvtools
 

+ 28 - 9
3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto

@@ -189,6 +189,7 @@ message Transformation {
     TransformationAddNoContractionDecoration add_no_contraction_decoration = 19;
     TransformationSetMemoryOperandsMask set_memory_operands_mask = 20;
     TransformationCompositeExtract composite_extract = 21;
+    TransformationVectorShuffle vector_shuffle = 22;
     // Add additional option using the next available number.
   }
 }
@@ -431,19 +432,15 @@ message TransformationReplaceConstantWithUniform {
 
 message TransformationReplaceIdWithSynonym {
 
-  // Replaces an id use with something known to be synonymous with that id use,
-  // e.g. because it was obtained via applying OpCopyObject
+  // Replaces a use of an id with an id that is known to be synonymous, e.g.
+  // because it was obtained via applying OpCopyObject
 
-  // Identifies the id use that is to be replaced
+  // The id use that is to be replaced
   IdUseDescriptor id_use_descriptor = 1;
 
-  // Identifies the data with which the id use is expected to be synonymous
-  DataDescriptor data_descriptor = 2;
+  // The synonymous id
+  uint32 synonymous_id = 2;
 
-  // In the case that a temporary is required to express the synonym (e.g. to
-  // obtain an element of a vector, provides a fresh id for the temporary;
-  // should be set to 0 if no temporary is required
-  uint32 fresh_id_for_temporary = 3;
 }
 
 message TransformationSetFunctionControl {
@@ -537,3 +534,25 @@ message TransformationSplitBlock {
   uint32 fresh_id = 2;
 
 }
+
+message TransformationVectorShuffle {
+
+  // A transformation that adds a vector shuffle instruction.
+
+  // A descriptor for an instruction in a block before which the new
+  // OpVectorShuffle instruction should be inserted
+  InstructionDescriptor instruction_to_insert_before = 1;
+
+  // Result id for the shuffle operation.
+  uint32 fresh_id = 2;
+
+  // Id of the first vector operand.
+  uint32 vector1 = 3;
+
+  // Id of the second vector operand.
+  uint32 vector2 = 4;
+
+  // Indices that indicate which components of the input vectors should be used.
+  repeated uint32 component = 5;
+
+}

+ 3 - 0
3rdparty/spirv-tools/source/fuzz/transformation.cpp

@@ -37,6 +37,7 @@
 #include "source/fuzz/transformation_set_memory_operands_mask.h"
 #include "source/fuzz/transformation_set_selection_control.h"
 #include "source/fuzz/transformation_split_block.h"
+#include "source/fuzz/transformation_vector_shuffle.h"
 #include "source/util/make_unique.h"
 
 namespace spvtools {
@@ -107,6 +108,8 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
           message.set_selection_control());
     case protobufs::Transformation::TransformationCase::kSplitBlock:
       return MakeUnique<TransformationSplitBlock>(message.split_block());
+    case protobufs::Transformation::TransformationCase::kVectorShuffle:
+      return MakeUnique<TransformationVectorShuffle>(message.vector_shuffle());
     case protobufs::Transformation::TRANSFORMATION_NOT_SET:
       assert(false && "An unset transformation was encountered.");
       return nullptr;

+ 2 - 0
3rdparty/spirv-tools/source/fuzz/transformation_composite_extract.cpp

@@ -99,6 +99,8 @@ void TransformationCompositeExtract::Apply(
 
   fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
 
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+
   // Add the fact that the id storing the extracted element is synonymous with
   // the index into the structure.
   std::vector<uint32_t> indices;

+ 39 - 149
3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.cpp

@@ -31,13 +31,9 @@ TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
     : message_(message) {}
 
 TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
-    protobufs::IdUseDescriptor id_use_descriptor,
-    protobufs::DataDescriptor data_descriptor,
-    uint32_t fresh_id_for_temporary) {
-  assert((fresh_id_for_temporary == 0) == (data_descriptor.index().empty()));
+    protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id) {
   *message_.mutable_id_use_descriptor() = std::move(id_use_descriptor);
-  *message_.mutable_data_descriptor() = std::move(data_descriptor);
-  message_.set_fresh_id_for_temporary(fresh_id_for_temporary);
+  message_.set_synonymous_id(synonymous_id);
 }
 
 bool TransformationReplaceIdWithSynonym::IsApplicable(
@@ -46,18 +42,10 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
   auto id_of_interest = message_.id_use_descriptor().id_of_interest();
 
   // Does the fact manager know about the synonym?
-  auto ids_with_known_synonyms = fact_manager.GetIdsForWhichSynonymsAreKnown();
-  if (std::find(ids_with_known_synonyms.begin(), ids_with_known_synonyms.end(),
-                id_of_interest) == ids_with_known_synonyms.end()) {
-    return false;
-  }
-
-  auto available_synonyms = fact_manager.GetSynonymsForId(id_of_interest);
-  if (std::find_if(available_synonyms.begin(), available_synonyms.end(),
-                   [this](const protobufs::DataDescriptor* dd) -> bool {
-                     return DataDescriptorEquals()(dd,
-                                                   &message_.data_descriptor());
-                   }) == available_synonyms.end()) {
+  auto data_descriptor_for_synonymous_id =
+      MakeDataDescriptor(message_.synonymous_id(), {});
+  if (!fact_manager.IsSynonymous(MakeDataDescriptor(id_of_interest, {}),
+                                 data_descriptor_for_synonymous_id, context)) {
     return false;
   }
 
@@ -68,34 +56,18 @@ bool TransformationReplaceIdWithSynonym::IsApplicable(
     return false;
   }
 
-  // Is it legitimate to replace the use identified by the id use descriptor
-  // with a synonym?
-  if (!ReplacingUseWithSynonymIsOk(
+  // Is the use suitable for being replaced in principle?
+  if (!UseCanBeReplacedWithSynonym(
           context, use_instruction,
-          message_.id_use_descriptor().in_operand_index(),
-          message_.data_descriptor())) {
+          message_.id_use_descriptor().in_operand_index())) {
     return false;
   }
 
-  if (message_.fresh_id_for_temporary() == 0) {
-    if (!message_.data_descriptor().index().empty()) {
-      // If we have no id to use as a temporary variable, we should not have any
-      // indices to extract from.
-      return false;
-    }
-  } else {
-    if (!fuzzerutil::IsFreshId(context, message_.fresh_id_for_temporary())) {
-      // The id to be used as a temporary needs to be fresh.
-      return false;
-    }
-    if (message_.data_descriptor().index_size() != 1) {
-      // At present we support just a single index to allow extracting directly
-      // from a composite.
-      return false;
-    }
-  }
-
-  return true;
+  // The transformation is applicable if the synonymous id is available at the
+  // use point.
+  return IdsIsAvailableAtUse(context, use_instruction,
+                             message_.id_use_descriptor().in_operand_index(),
+                             message_.synonymous_id());
 }
 
 void TransformationReplaceIdWithSynonym::Apply(
@@ -103,77 +75,9 @@ void TransformationReplaceIdWithSynonym::Apply(
     spvtools::fuzz::FactManager* /*unused*/) const {
   auto instruction_to_change =
       FindInstructionContainingUse(message_.id_use_descriptor(), context);
-
-  // Ultimately we are going to replace the id use identified in the
-  // transformation with |replacement_id|, which will either be the synonym's
-  // id, or the id of a temporary used to extract the synonym from a composite.
-  uint32_t replacement_id;
-
-  if (message_.fresh_id_for_temporary()) {
-    // The transformation having a temporary variable means that we need to
-    // extract the synonym from a composite.
-
-    uint32_t type_id_of_id_to_be_replaced =
-        context->get_def_use_mgr()
-            ->GetDef(message_.id_use_descriptor().id_of_interest())
-            ->type_id();
-    opt::analysis::Type* type_of_id_to_be_replaced =
-        context->get_type_mgr()->GetType(type_id_of_id_to_be_replaced);
-    opt::analysis::Type* type_of_composite = context->get_type_mgr()->GetType(
-        context->get_def_use_mgr()
-            ->GetDef(message_.data_descriptor().object())
-            ->type_id());
-
-    // Intuitively we want to make an OpCompositeExtract instruction, to get the
-    // synonym out of the composite. But in the case of a vector, the synonym
-    // might involve multiple vector indices; e.g. the y and z components of a
-    // vec4 might be synonymous with a vec2, and in that case OpCompositeExtract
-    // doesn't give us what we want; we need to use OpVectorShuffle instead.
-    std::unique_ptr<opt::Instruction> new_instruction;
-    if (type_of_composite->AsVector() &&
-        type_of_composite->AsVector()->element_type() !=
-            type_of_id_to_be_replaced) {
-      // We need to extract a vector from inside a vector, so we will need to
-      // use OpVectorShuffle.
-
-      assert(type_of_id_to_be_replaced->AsVector());
-      assert(type_of_id_to_be_replaced->AsVector()->element_type() ==
-             type_of_composite->AsVector()->element_type());
-      opt::Instruction::OperandList shuffle_operands = {
-          {SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}},
-          {SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}}};
-      for (uint32_t i = 0;
-           i < type_of_id_to_be_replaced->AsVector()->element_count(); i++) {
-        shuffle_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER,
-                                    {message_.data_descriptor().index(0) + i}});
-      }
-      new_instruction = MakeUnique<opt::Instruction>(
-          context, SpvOpVectorShuffle, type_id_of_id_to_be_replaced,
-          message_.fresh_id_for_temporary(), shuffle_operands);
-    } else {
-      // We are either extracting from a non-vector, or extracting a scalar from
-      // a vector, so we can use OpCompositeExtract.
-      opt::Instruction::OperandList extract_operands = {
-          {SPV_OPERAND_TYPE_ID, {message_.data_descriptor().object()}},
-          {SPV_OPERAND_TYPE_LITERAL_INTEGER,
-           {message_.data_descriptor().index(0)}}};
-      new_instruction = MakeUnique<opt::Instruction>(
-          context, SpvOpCompositeExtract, type_id_of_id_to_be_replaced,
-          message_.fresh_id_for_temporary(), extract_operands);
-    }
-    instruction_to_change->InsertBefore(std::move(new_instruction));
-
-    // The replacement id is the temporary variable we used to extract the
-    // synonym from a composite.
-    replacement_id = message_.fresh_id_for_temporary();
-    fuzzerutil::UpdateModuleIdBound(context, replacement_id);
-  } else {
-    // The replacement id is the synonym's id.
-    replacement_id = message_.data_descriptor().object();
-  }
-
   instruction_to_change->SetInOperand(
-      message_.id_use_descriptor().in_operand_index(), {replacement_id});
+      message_.id_use_descriptor().in_operand_index(),
+      {message_.synonymous_id()});
   context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
 }
 
@@ -184,22 +88,33 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
   return result;
 }
 
-bool TransformationReplaceIdWithSynonym::ReplacingUseWithSynonymIsOk(
+bool TransformationReplaceIdWithSynonym::IdsIsAvailableAtUse(
     opt::IRContext* context, opt::Instruction* use_instruction,
-    uint32_t use_in_operand_index, const protobufs::DataDescriptor& synonym) {
-  auto defining_instruction =
-      context->get_def_use_mgr()->GetDef(synonym.object());
-
-  if (use_instruction == defining_instruction) {
-    // If we have an instruction:
-    //   %a = OpCopyObject %t %b
-    // then we know %a and %b are synonymous, but we do *not* want to turn
-    // this into:
-    //   %a = OpCopyObject %t %a
-    // We require this special case because an instruction dominates itself.
+    uint32_t use_input_operand_index, uint32_t id) {
+  if (!context->get_instr_block(id)) {
+    return true;
+  }
+  auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
+  if (defining_instruction == use_instruction) {
     return false;
   }
+  auto dominator_analysis = context->GetDominatorAnalysis(
+      context->get_instr_block(use_instruction)->GetParent());
+  if (use_instruction->opcode() == SpvOpPhi) {
+    // In the case where the use is an operand to OpPhi, it is actually the
+    // *parent* block associated with the operand that must be dominated by
+    // the synonym.
+    auto parent_block =
+        use_instruction->GetSingleWordInOperand(use_input_operand_index + 1);
+    return dominator_analysis->Dominates(
+        context->get_instr_block(defining_instruction)->id(), parent_block);
+  }
+  return dominator_analysis->Dominates(defining_instruction, use_instruction);
+}
 
+bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
+    opt::IRContext* context, opt::Instruction* use_instruction,
+    uint32_t use_in_operand_index) {
   if (use_instruction->opcode() == SpvOpAccessChain &&
       use_in_operand_index > 0) {
     // This is an access chain index.  If the (sub-)object being accessed by the
@@ -273,31 +188,6 @@ bool TransformationReplaceIdWithSynonym::ReplacingUseWithSynonymIsOk(
       return false;
     }
   }
-
-  // We now need to check that replacing the use with the synonym will respect
-  // dominance rules - i.e. the synonym needs to dominate the use.
-  // This is only relevant if the defining instruction is in a block; if it is
-  // not in a block then it is at global scope, and so replacing the use with it
-  // is fine.
-  if (context->get_instr_block(defining_instruction)) {
-    auto dominator_analysis = context->GetDominatorAnalysis(
-        context->get_instr_block(use_instruction)->GetParent());
-    if (use_instruction->opcode() == SpvOpPhi) {
-      // In the case where the use is an operand to OpPhi, it is actually the
-      // *parent* block associated with the operand that must be dominated by
-      // the synonym.
-      auto parent_block =
-          use_instruction->GetSingleWordInOperand(use_in_operand_index + 1);
-      if (!dominator_analysis->Dominates(
-              context->get_instr_block(defining_instruction)->id(),
-              parent_block)) {
-        return false;
-      }
-    } else if (!dominator_analysis->Dominates(defining_instruction,
-                                              use_instruction)) {
-      return false;
-    }
-  }
   return true;
 }
 

+ 23 - 17
3rdparty/spirv-tools/source/fuzz/transformation_replace_id_with_synonym.h

@@ -29,40 +29,46 @@ class TransformationReplaceIdWithSynonym : public Transformation {
       const protobufs::TransformationReplaceIdWithSynonym& message);
 
   TransformationReplaceIdWithSynonym(
-      protobufs::IdUseDescriptor id_use_descriptor,
-      protobufs::DataDescriptor data_descriptor,
-      uint32_t fresh_id_for_temporary);
+      protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id);
 
   // - The fact manager must know that the id identified by
   //   |message_.id_use_descriptor| is synonomous with
-  //   |message_.data_descriptor|.
-  // - Replacing the id in |message_.id_use_descriptor| by the synonym in
-  //   |message_.data_descriptor| must respect SPIR-V's rules about uses being
+  //   |message_.synonymous_id|.
+  // - Replacing the id in |message_.id_use_descriptor| by
+  //   |message_.synonymous_id| must respect SPIR-V's rules about uses being
   //   dominated by their definitions.
   // - The id must not be an index into an access chain whose base object has
   //   struct type, as such indices must be constants.
   // - The id must not be a pointer argument to a function call (because the
   //   synonym might not be a memory object declaration).
   // - |fresh_id_for_temporary| must be 0.
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): the
-  //  motivation for the temporary is to support the case where an id is
-  //  synonymous with an element of a composite.  Until support for that is
-  //  implemented, 0 records that no temporary is needed.
   bool IsApplicable(opt::IRContext* context,
                     const FactManager& fact_manager) const override;
 
   // Replaces the use identified by |message_.id_use_descriptor| with the
-  // synonymous id identified by |message_.data_descriptor|.
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2855): in due
-  //  course it will also be necessary to add an additional instruction to pull
-  //  the synonym out of a composite.
+  // synonymous id identified by |message_.synonymous_id|.
   void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
 
   protobufs::Transformation ToMessage() const override;
 
-  static bool ReplacingUseWithSynonymIsOk(
-      opt::IRContext* context, opt::Instruction* use_instruction,
-      uint32_t use_in_operand_index, const protobufs::DataDescriptor& synonym);
+  // Checks whether the |id| is available (according to dominance rules) at the
+  // use point defined by input operand |use_input_operand_index| of
+  // |use_instruction|.
+  static bool IdsIsAvailableAtUse(opt::IRContext* context,
+                                  opt::Instruction* use_instruction,
+                                  uint32_t use_input_operand_index,
+                                  uint32_t id);
+
+  // Checks whether various conditions hold related to the acceptability of
+  // replacing the id use at |use_in_operand_index| of |use_instruction| with
+  // a synonym.  In particular, this checks that:
+  // - the id use is not an index into a struct field in an OpAccessChain - such
+  //   indices must be constants, so it is dangerous to replace them.
+  // - the id use is not a pointer function call argument, on which there are
+  //   restrictions that make replacement problematic.
+  static bool UseCanBeReplacedWithSynonym(opt::IRContext* context,
+                                          opt::Instruction* use_instruction,
+                                          uint32_t use_in_operand_index);
 
  private:
   protobufs::TransformationReplaceIdWithSynonym message_;

+ 203 - 0
3rdparty/spirv-tools/source/fuzz/transformation_vector_shuffle.cpp

@@ -0,0 +1,203 @@
+// 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_vector_shuffle.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationVectorShuffle::TransformationVectorShuffle(
+    const spvtools::fuzz::protobufs::TransformationVectorShuffle& message)
+    : message_(message) {}
+
+TransformationVectorShuffle::TransformationVectorShuffle(
+    const protobufs::InstructionDescriptor& instruction_to_insert_before,
+    uint32_t fresh_id, uint32_t vector1, uint32_t vector2,
+    const std::vector<uint32_t>& component) {
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+  message_.set_fresh_id(fresh_id);
+  message_.set_vector1(vector1);
+  message_.set_vector2(vector2);
+  for (auto a_component : component) {
+    message_.add_component(a_component);
+  }
+}
+
+bool TransformationVectorShuffle::IsApplicable(
+    opt::IRContext* context,
+    const spvtools::fuzz::FactManager& /*unused*/) const {
+  // The fresh id must not already be in use.
+  if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
+    return false;
+  }
+  // The instruction before which the shuffle will be inserted must exist.
+  auto instruction_to_insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), context);
+  if (!instruction_to_insert_before) {
+    return false;
+  }
+  // The first vector must be an instruction with a type id
+  auto vector1_instruction =
+      context->get_def_use_mgr()->GetDef(message_.vector1());
+  if (!vector1_instruction || !vector1_instruction->type_id()) {
+    return false;
+  }
+  // The second vector must be an instruction with a type id
+  auto vector2_instruction =
+      context->get_def_use_mgr()->GetDef(message_.vector2());
+  if (!vector2_instruction || !vector2_instruction->type_id()) {
+    return false;
+  }
+  auto vector1_type =
+      context->get_type_mgr()->GetType(vector1_instruction->type_id());
+  // The first vector instruction's type must actually be a vector type.
+  if (!vector1_type->AsVector()) {
+    return false;
+  }
+  auto vector2_type =
+      context->get_type_mgr()->GetType(vector2_instruction->type_id());
+  // The second vector instruction's type must actually be a vector type.
+  if (!vector2_type->AsVector()) {
+    return false;
+  }
+  // The element types of the vectors must be the same.
+  if (vector1_type->AsVector()->element_type() !=
+      vector2_type->AsVector()->element_type()) {
+    return false;
+  }
+  uint32_t combined_size = vector1_type->AsVector()->element_count() +
+                           vector2_type->AsVector()->element_count();
+  for (auto a_compoment : message_.component()) {
+    // 0xFFFFFFFF is used to represent an undefined component.  Unless
+    // undefined, a component must be less than the combined size of the
+    // vectors.
+    if (a_compoment != 0xFFFFFFFF && a_compoment >= combined_size) {
+      return false;
+    }
+  }
+  // The module must already declare an appropriate type in which to store the
+  // result of the shuffle.
+  if (!GetResultTypeId(context, *vector1_type->AsVector()->element_type())) {
+    return false;
+  }
+  // Each of the vectors used in the shuffle must be available at the insertion
+  // point.
+  for (auto used_instruction : {vector1_instruction, vector2_instruction}) {
+    if (auto block = context->get_instr_block(used_instruction)) {
+      if (!context->GetDominatorAnalysis(block->GetParent())
+               ->Dominates(used_instruction, instruction_to_insert_before)) {
+        return false;
+      }
+    }
+  }
+
+  // It must be legitimate to insert an OpVectorShuffle before the identified
+  // instruction.
+  return fuzzerutil::CanInsertOpcodeBeforeInstruction(
+      SpvOpVectorShuffle, instruction_to_insert_before);
+}
+
+void TransformationVectorShuffle::Apply(
+    opt::IRContext* context, spvtools::fuzz::FactManager* fact_manager) const {
+  // Make input operands for a shuffle instruction - these comprise the two
+  // vectors being shuffled, followed by the integer literal components.
+  opt::Instruction::OperandList shuffle_operands = {
+      {SPV_OPERAND_TYPE_ID, {message_.vector1()}},
+      {SPV_OPERAND_TYPE_ID, {message_.vector2()}}};
+  for (auto a_component : message_.component()) {
+    shuffle_operands.push_back(
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {a_component}});
+  }
+
+  uint32_t result_type_id = GetResultTypeId(
+      context, *GetVectorType(context, message_.vector1())->element_type());
+
+  // Add a shuffle instruction right before the instruction identified by
+  // |message_.instruction_to_insert_before|.
+  FindInstruction(message_.instruction_to_insert_before(), context)
+      ->InsertBefore(MakeUnique<opt::Instruction>(
+          context, SpvOpVectorShuffle, result_type_id, message_.fresh_id(),
+          shuffle_operands));
+  fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id());
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+
+  // Add synonym facts relating the defined elements of the shuffle result to
+  // the vector components that they come from.
+  for (uint32_t component_index = 0;
+       component_index < static_cast<uint32_t>(message_.component_size());
+       component_index++) {
+    uint32_t component = message_.component(component_index);
+    if (component == 0xFFFFFFFF) {
+      // This component is undefined, so move on - but first note that the
+      // overall shuffle result cannot be synonymous with any vector.
+      continue;
+    }
+
+    // This describes the element of the result vector associated with
+    // |component_index|.
+    protobufs::DataDescriptor descriptor_for_result_component =
+        MakeDataDescriptor(message_.fresh_id(), {component_index});
+
+    protobufs::DataDescriptor descriptor_for_source_component;
+
+    // Get a data descriptor for the component of the input vector to which
+    // |component| refers.
+    if (component <
+        GetVectorType(context, message_.vector1())->element_count()) {
+      descriptor_for_source_component =
+          MakeDataDescriptor(message_.vector1(), {component});
+    } else {
+      auto index_into_vector_2 =
+          component -
+          GetVectorType(context, message_.vector1())->element_count();
+      assert(index_into_vector_2 <
+                 GetVectorType(context, message_.vector2())->element_count() &&
+             "Vector shuffle index is out of bounds.");
+      descriptor_for_source_component =
+          MakeDataDescriptor(message_.vector2(), {index_into_vector_2});
+    }
+
+    // Add a fact relating this input vector component with the associated
+    // result component.
+    fact_manager->AddFactDataSynonym(descriptor_for_result_component,
+                                     descriptor_for_source_component, context);
+  }
+}
+
+protobufs::Transformation TransformationVectorShuffle::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_vector_shuffle() = message_;
+  return result;
+}
+
+uint32_t TransformationVectorShuffle::GetResultTypeId(
+    opt::IRContext* context, const opt::analysis::Type& element_type) const {
+  opt::analysis::Vector result_type(
+      &element_type, static_cast<uint32_t>(message_.component_size()));
+  return context->get_type_mgr()->GetId(&result_type);
+}
+
+opt::analysis::Vector* TransformationVectorShuffle::GetVectorType(
+    opt::IRContext* context, uint32_t id_of_vector) {
+  return context->get_type_mgr()
+      ->GetType(context->get_def_use_mgr()->GetDef(id_of_vector)->type_id())
+      ->AsVector();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 82 - 0
3rdparty/spirv-tools/source/fuzz/transformation_vector_shuffle.h

@@ -0,0 +1,82 @@
+// 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_VECTOR_SHUFFLE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_VECTOR_SHUFFLE_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/types.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationVectorShuffle : public Transformation {
+ public:
+  explicit TransformationVectorShuffle(
+      const protobufs::TransformationVectorShuffle& message);
+
+  TransformationVectorShuffle(
+      const protobufs::InstructionDescriptor& instruction_to_insert_before,
+      uint32_t fresh_id, uint32_t vector1, uint32_t vector2,
+      const std::vector<uint32_t>& component);
+
+  // - |message_.fresh_id| must not be in use
+  // - |message_.instruction_to_insert_before| must identify an instruction
+  //   before which it is legitimate to insert an OpVectorShuffle
+  // - |message_.vector1| and |message_.vector2| must be instructions of vector
+  //   type, and the element types of these vectors must be the same
+  // - Each element of |message_.component| must either be 0xFFFFFFFF
+  //   (representing an undefined component), or must be less than the combined
+  //   sizes of the input vectors
+  // - The module must already contain a vector type with the same element type
+  //   as |message_.vector1| and |message_.vector2|, and with the size of
+  //   |message_component| as its element count
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // Inserts an OpVectorShuffle instruction before
+  // |message_.instruction_to_insert_before|, shuffles vectors
+  // |message_.vector1| and |message_.vector2| using the indices provided by
+  // |message_.component|, into |message_.fresh_id|.  Adds a fact to the fact
+  // manager recording the fact each element of |message_.fresh_id| is
+  // synonymous with the element of |message_.vector1| or |message_.vector2|
+  // from which it came (with undefined components being ignored).  If the
+  // result vector is a contiguous sub-range of one of the input vectors, a
+  // fact is added to record that |message_.fresh_id| is synonymous with this
+  // sub-range.
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // Returns a type id that already exists in |context| suitable for
+  // representing the result of the shuffle, where |element_type| is known to
+  // be the common element type of the vectors to which the shuffle is being
+  // applied.  Returns 0 if no such id exists.
+  uint32_t GetResultTypeId(opt::IRContext* context,
+                           const opt::analysis::Type& element_type) const;
+
+  static opt::analysis::Vector* GetVectorType(opt::IRContext* context,
+                                              uint32_t id_of_vector);
+
+  protobufs::TransformationVectorShuffle message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_VECTOR_SHUFFLE_H_

+ 10 - 5
3rdparty/spirv-tools/source/val/validate_misc.cpp

@@ -42,13 +42,18 @@ spv_result_t ValidateUndef(ValidationState_t& _, const Instruction* inst) {
 
 spv_result_t ValidateShaderClock(ValidationState_t& _,
                                  const Instruction* inst) {
-// #2952: disabled until scope discussion is resolved.
-#if 0
-  const uint32_t execution_scope = inst->word(3);
-  if (auto error = ValidateExecutionScope(_, inst, execution_scope)) {
+  const uint32_t scope = inst->GetOperandAs<uint32_t>(2);
+  if (auto error = ValidateScope(_, inst, scope)) {
     return error;
   }
-#endif
+
+  bool is_int32 = false, is_const_int32 = false;
+  uint32_t value = 0;
+  std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
+  if (is_const_int32 && value != SpvScopeSubgroup && value != SpvScopeDevice) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << "Scope must be Subgroup or Device";
+  }
 
   // Result Type must be a 64 - bit unsigned integer type or
   // a vector of two - components of 32 -

+ 23 - 27
3rdparty/spirv-tools/source/val/validate_scopes.cpp

@@ -39,8 +39,8 @@ bool IsValidScope(uint32_t scope) {
   return false;
 }
 
-spv_result_t ValidateExecutionScope(ValidationState_t& _,
-                                    const Instruction* inst, uint32_t scope) {
+spv_result_t ValidateScope(ValidationState_t& _, const Instruction* inst,
+                           uint32_t scope) {
   SpvOp opcode = inst->opcode();
   bool is_int32 = false, is_const_int32 = false;
   uint32_t value = 0;
@@ -48,8 +48,7 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
 
   if (!is_int32) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": expected Execution Scope to be a 32-bit int";
+           << spvOpcodeString(opcode) << ": expected scope to be a 32-bit int";
   }
 
   if (!is_const_int32) {
@@ -66,7 +65,6 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
              << "Scope ids must be constant or specialization constant when "
              << "CooperativeMatrixNV capability is present";
     }
-    return SPV_SUCCESS;
   }
 
   if (is_const_int32 && !IsValidScope(value)) {
@@ -74,6 +72,24 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
            << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
   }
 
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateExecutionScope(ValidationState_t& _,
+                                    const Instruction* inst, uint32_t scope) {
+  SpvOp opcode = inst->opcode();
+  bool is_int32 = false, is_const_int32 = false;
+  uint32_t value = 0;
+  std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
+
+  if (auto error = ValidateScope(_, inst, scope)) {
+    return error;
+  }
+
+  if (!is_const_int32) {
+    return SPV_SUCCESS;
+  }
+
   // Vulkan specific rules
   if (spvIsVulkanEnv(_.context()->target_env)) {
     // Vulkan 1.1 specific rules
@@ -152,34 +168,14 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
   uint32_t value = 0;
   std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(scope);
 
-  if (!is_int32) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << spvOpcodeString(opcode)
-           << ": expected Memory Scope to be a 32-bit int";
+  if (auto error = ValidateScope(_, inst, scope)) {
+    return error;
   }
 
   if (!is_const_int32) {
-    if (_.HasCapability(SpvCapabilityShader) &&
-        !_.HasCapability(SpvCapabilityCooperativeMatrixNV)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Scope ids must be OpConstant when Shader capability is "
-             << "present";
-    }
-    if (_.HasCapability(SpvCapabilityShader) &&
-        _.HasCapability(SpvCapabilityCooperativeMatrixNV) &&
-        !spvOpcodeIsConstant(_.GetIdOpcode(scope))) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Scope ids must be constant or specialization constant when "
-             << "CooperativeMatrixNV capability is present";
-    }
     return SPV_SUCCESS;
   }
 
-  if (is_const_int32 && !IsValidScope(value)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
-  }
-
   if (value == SpvScopeQueueFamilyKHR) {
     if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
       return SPV_SUCCESS;

+ 3 - 0
3rdparty/spirv-tools/source/val/validate_scopes.h

@@ -20,6 +20,9 @@
 namespace spvtools {
 namespace val {
 
+spv_result_t ValidateScope(ValidationState_t& _, const Instruction* inst,
+                           uint32_t scope);
+
 spv_result_t ValidateExecutionScope(ValidationState_t& _,
                                     const Instruction* inst, uint32_t scope);
 

+ 3 - 0
3rdparty/spirv-tools/test/fuzz/CMakeLists.txt

@@ -17,6 +17,7 @@ if (${SPIRV_BUILD_FUZZER})
   set(SOURCES
           fuzz_test_util.h
 
+          data_synonym_transformation_test.cpp
           equivalence_relation_test.cpp
           fact_manager_test.cpp
           fuzz_test_util.cpp
@@ -37,11 +38,13 @@ if (${SPIRV_BUILD_FUZZER})
           transformation_move_block_down_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
+          transformation_replace_id_with_synonym_test.cpp
           transformation_set_function_control_test.cpp
           transformation_set_loop_control_test.cpp
           transformation_set_memory_operands_mask_test.cpp
           transformation_set_selection_control_test.cpp
           transformation_split_block_test.cpp
+          transformation_vector_shuffle_test.cpp
           uniform_buffer_element_descriptor_test.cpp)
 
   if (${SPIRV_ENABLE_LONG_FUZZER_TESTS})

+ 1122 - 0
3rdparty/spirv-tools/test/fuzz/data_synonym_transformation_test.cpp

@@ -0,0 +1,1122 @@
+// 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/data_descriptor.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_composite_extract.h"
+#include "source/fuzz/transformation_replace_id_with_synonym.h"
+#include "source/fuzz/transformation_vector_shuffle.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+// This file captures tests that check correctness of the collective use of a
+// number of transformations that relate to data synonyms.
+
+protobufs::Fact MakeSynonymFact(uint32_t first_id,
+                                std::vector<uint32_t>&& first_indices,
+                                uint32_t second_id,
+                                std::vector<uint32_t>&& second_indices) {
+  protobufs::FactDataSynonym data_synonym_fact;
+  *data_synonym_fact.mutable_data1() =
+      MakeDataDescriptor(first_id, std::move(first_indices));
+  *data_synonym_fact.mutable_data2() =
+      MakeDataDescriptor(second_id, std::move(second_indices));
+  protobufs::Fact result;
+  *result.mutable_data_synonym_fact() = data_synonym_fact;
+  return result;
+}
+
+TEST(DataSynonymTransformationTest, ArrayCompositeSynonyms) {
+  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 %11 "A"
+               OpName %20 "B"
+               OpName %31 "g"
+               OpName %35 "h"
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 3
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 0
+         %13 = OpConstant %6 3
+         %14 = OpTypePointer Function %6
+         %16 = OpTypeFloat 32
+         %17 = OpConstant %7 4
+         %18 = OpTypeArray %16 %17
+         %19 = OpTypePointer Function %18
+         %24 = OpTypePointer Function %16
+         %28 = OpConstant %16 42
+         %30 = OpConstant %6 2
+         %34 = OpConstant %6 1
+         %38 = OpConstant %6 42
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %20 = OpVariable %19 Function
+         %31 = OpVariable %24 Function
+         %35 = OpVariable %14 Function
+         %15 = OpAccessChain %14 %11 %12
+         %21 = OpAccessChain %14 %11 %12
+         %22 = OpLoad %6 %21
+        %100 = OpCompositeConstruct %9 %12 %13 %22
+               OpStore %15 %13
+         %23 = OpConvertSToF %16 %22
+         %25 = OpAccessChain %24 %20 %12
+               OpStore %25 %23
+         %26 = OpAccessChain %14 %11 %12
+         %27 = OpLoad %6 %26
+         %29 = OpAccessChain %24 %20 %27
+               OpStore %29 %28
+         %32 = OpLoad %16 %31
+        %101 = OpCompositeConstruct %18 %28 %23 %32 %23
+         %50 = OpCopyObject %16 %23
+         %51 = OpCopyObject %16 %23
+         %33 = OpAccessChain %24 %20 %30
+               OpStore %33 %28
+               OpStore %33 %32
+         %36 = OpLoad %6 %35
+         %37 = OpAccessChain %14 %11 %34
+               OpStore %37 %36
+         %39 = OpAccessChain %14 %11 %12
+         %40 = OpLoad %6 %39
+         %41 = OpIAdd %6 %38 %40
+         %42 = OpAccessChain %14 %11 %30
+               OpStore %42 %41
+               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;
+  fact_manager.AddFact(MakeSynonymFact(12, {}, 100, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(13, {}, 100, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(22, {}, 100, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(28, {}, 101, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(23, {}, 101, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(32, {}, 101, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(23, {}, 101, {3}), context.get());
+
+  // Replace %12 with %100[0] in '%25 = OpAccessChain %24 %20 %12'
+  auto instruction_descriptor_1 =
+      MakeInstructionDescriptor(25, SpvOpAccessChain, 0);
+  auto good_extract_1 =
+      TransformationCompositeExtract(instruction_descriptor_1, 102, 100, {0});
+  // Bad: id already in use
+  auto bad_extract_1 = TransformationCompositeExtract(
+      MakeInstructionDescriptor(25, SpvOpAccessChain, 0), 25, 100, {0});
+  ASSERT_TRUE(good_extract_1.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_extract_1.IsApplicable(context.get(), fact_manager));
+  good_extract_1.Apply(context.get(), &fact_manager);
+  auto replacement_1 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(12, instruction_descriptor_1, 1), 102);
+  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
+  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %13 with %100[1] in 'OpStore %15 %13'
+  auto instruction_descriptor_2 = MakeInstructionDescriptor(100, SpvOpStore, 0);
+  auto good_extract_2 =
+      TransformationCompositeExtract(instruction_descriptor_2, 103, 100, {1});
+  // No bad example provided here.
+  ASSERT_TRUE(good_extract_2.IsApplicable(context.get(), fact_manager));
+  good_extract_2.Apply(context.get(), &fact_manager);
+  auto replacement_2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(13, instruction_descriptor_2, 1), 103);
+  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
+  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %22 with %100[2] in '%23 = OpConvertSToF %16 %22'
+  auto instruction_descriptor_3 =
+      MakeInstructionDescriptor(23, SpvOpConvertSToF, 0);
+  auto good_extract_3 =
+      TransformationCompositeExtract(instruction_descriptor_3, 104, 100, {2});
+  ASSERT_TRUE(good_extract_3.IsApplicable(context.get(), fact_manager));
+  good_extract_3.Apply(context.get(), &fact_manager);
+  auto replacement_3 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(22, instruction_descriptor_3, 0), 104);
+  // Bad: wrong input operand index
+  auto bad_replacement_3 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(22, instruction_descriptor_3, 1), 104);
+  ASSERT_TRUE(replacement_3.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_replacement_3.IsApplicable(context.get(), fact_manager));
+  replacement_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %28 with %101[0] in 'OpStore %33 %28'
+  auto instruction_descriptor_4 = MakeInstructionDescriptor(33, SpvOpStore, 0);
+  auto good_extract_4 =
+      TransformationCompositeExtract(instruction_descriptor_4, 105, 101, {0});
+  // Bad: instruction descriptor does not identify an appropriate instruction
+  auto bad_extract_4 = TransformationCompositeExtract(
+      MakeInstructionDescriptor(33, SpvOpCopyObject, 0), 105, 101, {0});
+  ASSERT_TRUE(good_extract_4.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_extract_4.IsApplicable(context.get(), fact_manager));
+  good_extract_4.Apply(context.get(), &fact_manager);
+  auto replacement_4 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(28, instruction_descriptor_4, 1), 105);
+  ASSERT_TRUE(replacement_4.IsApplicable(context.get(), fact_manager));
+  replacement_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %23 with %101[1] in '%50 = OpCopyObject %16 %23'
+  auto instruction_descriptor_5 =
+      MakeInstructionDescriptor(50, SpvOpCopyObject, 0);
+  auto good_extract_5 =
+      TransformationCompositeExtract(instruction_descriptor_5, 106, 101, {1});
+  ASSERT_TRUE(good_extract_5.IsApplicable(context.get(), fact_manager));
+  good_extract_5.Apply(context.get(), &fact_manager);
+  auto replacement_5 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(23, instruction_descriptor_5, 0), 106);
+  // Bad: wrong synonym fact being used
+  auto bad_replacement_5 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(23, instruction_descriptor_5, 0), 105);
+  ASSERT_TRUE(replacement_5.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_replacement_5.IsApplicable(context.get(), fact_manager));
+  replacement_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %32 with %101[2] in 'OpStore %33 %32'
+  auto instruction_descriptor_6 = MakeInstructionDescriptor(33, SpvOpStore, 1);
+  auto good_extract_6 =
+      TransformationCompositeExtract(instruction_descriptor_6, 107, 101, {2});
+  // Bad: id 1001 does not exist
+  auto bad_extract_6 =
+      TransformationCompositeExtract(instruction_descriptor_6, 107, 1001, {2});
+  ASSERT_TRUE(good_extract_6.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_extract_6.IsApplicable(context.get(), fact_manager));
+  good_extract_6.Apply(context.get(), &fact_manager);
+  auto replacement_6 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(32, instruction_descriptor_6, 1), 107);
+  ASSERT_TRUE(replacement_6.IsApplicable(context.get(), fact_manager));
+  replacement_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %23 with %101[3] in '%51 = OpCopyObject %16 %23'
+  auto instruction_descriptor_7 =
+      MakeInstructionDescriptor(51, SpvOpCopyObject, 0);
+  auto good_extract_7 =
+      TransformationCompositeExtract(instruction_descriptor_7, 108, 101, {3});
+  ASSERT_TRUE(good_extract_7.IsApplicable(context.get(), fact_manager));
+  good_extract_7.Apply(context.get(), &fact_manager);
+  auto replacement_7 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(23, instruction_descriptor_7, 0), 108);
+  // Bad: use id 0 is invalid
+  auto bad_replacement_7 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(0, instruction_descriptor_7, 0), 108);
+  ASSERT_TRUE(replacement_7.IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(bad_replacement_7.IsApplicable(context.get(), fact_manager));
+  replacement_7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %11 "A"
+               OpName %20 "B"
+               OpName %31 "g"
+               OpName %35 "h"
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+               OpDecorate %35 RelaxedPrecision
+               OpDecorate %36 RelaxedPrecision
+               OpDecorate %40 RelaxedPrecision
+               OpDecorate %41 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 3
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 0
+         %13 = OpConstant %6 3
+         %14 = OpTypePointer Function %6
+         %16 = OpTypeFloat 32
+         %17 = OpConstant %7 4
+         %18 = OpTypeArray %16 %17
+         %19 = OpTypePointer Function %18
+         %24 = OpTypePointer Function %16
+         %28 = OpConstant %16 42
+         %30 = OpConstant %6 2
+         %34 = OpConstant %6 1
+         %38 = OpConstant %6 42
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %20 = OpVariable %19 Function
+         %31 = OpVariable %24 Function
+         %35 = OpVariable %14 Function
+         %15 = OpAccessChain %14 %11 %12
+         %21 = OpAccessChain %14 %11 %12
+         %22 = OpLoad %6 %21
+        %100 = OpCompositeConstruct %9 %12 %13 %22
+        %103 = OpCompositeExtract %6 %100 1
+               OpStore %15 %103
+        %104 = OpCompositeExtract %6 %100 2
+         %23 = OpConvertSToF %16 %104
+        %102 = OpCompositeExtract %6 %100 0
+         %25 = OpAccessChain %24 %20 %102
+               OpStore %25 %23
+         %26 = OpAccessChain %14 %11 %12
+         %27 = OpLoad %6 %26
+         %29 = OpAccessChain %24 %20 %27
+               OpStore %29 %28
+         %32 = OpLoad %16 %31
+        %101 = OpCompositeConstruct %18 %28 %23 %32 %23
+        %106 = OpCompositeExtract %16 %101 1
+         %50 = OpCopyObject %16 %106
+        %108 = OpCompositeExtract %16 %101 3
+         %51 = OpCopyObject %16 %108
+         %33 = OpAccessChain %24 %20 %30
+        %105 = OpCompositeExtract %16 %101 0
+               OpStore %33 %105
+        %107 = OpCompositeExtract %16 %101 2
+               OpStore %33 %107
+         %36 = OpLoad %6 %35
+         %37 = OpAccessChain %14 %11 %34
+               OpStore %37 %36
+         %39 = OpAccessChain %14 %11 %12
+         %40 = OpLoad %6 %39
+         %41 = OpIAdd %6 %38 %40
+         %42 = OpAccessChain %14 %11 %30
+               OpStore %42 %41
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(DataSynonymTransformationTest, MatrixCompositeSynonyms) {
+  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 %10 "m"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+         %50 = OpUndef %7
+          %8 = OpTypeMatrix %7 3
+          %9 = OpTypePointer Function %8
+         %11 = OpTypeInt 32 1
+         %12 = OpConstant %11 0
+         %13 = OpConstant %6 1
+         %14 = OpConstantComposite %7 %13 %13 %13 %13
+         %15 = OpTypePointer Function %7
+         %17 = OpConstant %11 1
+         %18 = OpConstant %6 2
+         %19 = OpConstantComposite %7 %18 %18 %18 %18
+         %21 = OpConstant %11 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+         %16 = OpAccessChain %15 %10 %12
+               OpStore %16 %14
+         %20 = OpAccessChain %15 %10 %17
+               OpStore %20 %19
+         %22 = OpAccessChain %15 %10 %12
+         %23 = OpLoad %7 %22
+         %24 = OpAccessChain %15 %10 %17
+         %25 = OpLoad %7 %24
+        %100 = OpCompositeConstruct %8 %23 %25 %50
+         %26 = OpFAdd %7 %23 %25
+         %27 = OpAccessChain %15 %10 %21
+               OpStore %27 %26
+               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;
+  fact_manager.AddFact(MakeSynonymFact(23, {}, 100, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(25, {}, 100, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(50, {}, 100, {2}), context.get());
+
+  // Replace %23 with %100[0] in '%26 = OpFAdd %7 %23 %25'
+  auto instruction_descriptor_1 = MakeInstructionDescriptor(26, SpvOpFAdd, 0);
+  auto extract_1 =
+      TransformationCompositeExtract(instruction_descriptor_1, 101, 100, {0});
+  ASSERT_TRUE(extract_1.IsApplicable(context.get(), fact_manager));
+  extract_1.Apply(context.get(), &fact_manager);
+  auto replacement_1 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(23, instruction_descriptor_1, 0), 101);
+  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
+  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %25 with %100[1] in '%26 = OpFAdd %7 %23 %25'
+  auto instruction_descriptor_2 = MakeInstructionDescriptor(26, SpvOpFAdd, 0);
+  auto extract_2 =
+      TransformationCompositeExtract(instruction_descriptor_2, 102, 100, {1});
+  ASSERT_TRUE(extract_2.IsApplicable(context.get(), fact_manager));
+  extract_2.Apply(context.get(), &fact_manager);
+  auto replacement_2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(25, instruction_descriptor_2, 1), 102);
+  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
+  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "m"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+         %50 = OpUndef %7
+          %8 = OpTypeMatrix %7 3
+          %9 = OpTypePointer Function %8
+         %11 = OpTypeInt 32 1
+         %12 = OpConstant %11 0
+         %13 = OpConstant %6 1
+         %14 = OpConstantComposite %7 %13 %13 %13 %13
+         %15 = OpTypePointer Function %7
+         %17 = OpConstant %11 1
+         %18 = OpConstant %6 2
+         %19 = OpConstantComposite %7 %18 %18 %18 %18
+         %21 = OpConstant %11 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+         %16 = OpAccessChain %15 %10 %12
+               OpStore %16 %14
+         %20 = OpAccessChain %15 %10 %17
+               OpStore %20 %19
+         %22 = OpAccessChain %15 %10 %12
+         %23 = OpLoad %7 %22
+         %24 = OpAccessChain %15 %10 %17
+         %25 = OpLoad %7 %24
+        %100 = OpCompositeConstruct %8 %23 %25 %50
+        %101 = OpCompositeExtract %7 %100 0
+        %102 = OpCompositeExtract %7 %100 1
+         %26 = OpFAdd %7 %101 %102
+         %27 = OpAccessChain %15 %10 %21
+               OpStore %27 %26
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(DataSynonymTransformationTest, StructCompositeSynonyms) {
+  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 %9 "Inner"
+               OpMemberName %9 0 "a"
+               OpMemberName %9 1 "b"
+               OpName %11 "i1"
+               OpName %17 "i2"
+               OpName %31 "Point"
+               OpMemberName %31 0 "x"
+               OpMemberName %31 1 "y"
+               OpMemberName %31 2 "z"
+               OpName %32 "Outer"
+               OpMemberName %32 0 "c"
+               OpMemberName %32 1 "d"
+               OpName %34 "o1"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeVector %7 2
+          %9 = OpTypeStruct %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %7 2
+         %14 = OpConstant %7 3
+         %15 = OpConstantComposite %8 %13 %14
+         %16 = OpConstantComposite %9 %12 %15
+         %18 = OpConstant %6 0
+         %19 = OpTypePointer Function %6
+         %24 = OpTypePointer Function %8
+         %27 = OpConstant %7 4
+         %31 = OpTypeStruct %7 %7 %7
+         %32 = OpTypeStruct %9 %31
+         %33 = OpTypePointer Function %32
+         %36 = OpConstant %7 10
+         %37 = OpTypeInt 32 0
+         %38 = OpConstant %37 0
+         %39 = OpTypePointer Function %7
+         %42 = OpConstant %37 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %17 = OpVariable %10 Function
+         %34 = OpVariable %33 Function
+        %101 = OpCompositeConstruct %31 %27 %36 %27
+               OpStore %11 %16
+         %20 = OpAccessChain %19 %11 %18
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %21 %12
+        %102 = OpCompositeConstruct %9 %22 %15
+         %23 = OpAccessChain %19 %17 %18
+               OpStore %23 %22
+         %25 = OpAccessChain %24 %17 %12
+         %26 = OpLoad %8 %25
+         %28 = OpCompositeConstruct %8 %27 %27
+         %29 = OpFAdd %8 %26 %28
+         %30 = OpAccessChain %24 %17 %12
+               OpStore %30 %29
+         %35 = OpLoad %9 %11
+         %40 = OpAccessChain %39 %11 %12 %38
+         %41 = OpLoad %7 %40
+         %43 = OpAccessChain %39 %11 %12 %42
+         %44 = OpLoad %7 %43
+         %45 = OpCompositeConstruct %31 %36 %41 %44
+        %100 = OpCompositeConstruct %32 %16 %45
+         %46 = OpCompositeConstruct %32 %35 %45
+               OpStore %34 %46
+               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;
+
+  fact_manager.AddFact(MakeSynonymFact(16, {}, 100, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(45, {}, 100, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(27, {}, 101, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(36, {}, 101, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(27, {}, 101, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(22, {}, 102, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(15, {}, 102, {1}), context.get());
+
+  // Replace %45 with %100[1] in '%46 = OpCompositeConstruct %32 %35 %45'
+  auto instruction_descriptor_1 =
+      MakeInstructionDescriptor(46, SpvOpCompositeConstruct, 0);
+  auto extract_1 =
+      TransformationCompositeExtract(instruction_descriptor_1, 201, 100, {1});
+  ASSERT_TRUE(extract_1.IsApplicable(context.get(), fact_manager));
+  extract_1.Apply(context.get(), &fact_manager);
+  auto replacement_1 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(45, instruction_descriptor_1, 1), 201);
+  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
+  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace second occurrence of %27 with %101[0] in '%28 =
+  // OpCompositeConstruct %8 %27 %27'
+  auto instruction_descriptor_2 =
+      MakeInstructionDescriptor(28, SpvOpCompositeConstruct, 0);
+  auto extract_2 =
+      TransformationCompositeExtract(instruction_descriptor_2, 202, 101, {0});
+  ASSERT_TRUE(extract_2.IsApplicable(context.get(), fact_manager));
+  extract_2.Apply(context.get(), &fact_manager);
+  auto replacement_2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(27, instruction_descriptor_2, 1), 202);
+  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
+  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %36 with %101[1] in '%45 = OpCompositeConstruct %31 %36 %41 %44'
+  auto instruction_descriptor_3 =
+      MakeInstructionDescriptor(45, SpvOpCompositeConstruct, 0);
+  auto extract_3 =
+      TransformationCompositeExtract(instruction_descriptor_3, 203, 101, {1});
+  ASSERT_TRUE(extract_3.IsApplicable(context.get(), fact_manager));
+  extract_3.Apply(context.get(), &fact_manager);
+  auto replacement_3 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(36, instruction_descriptor_3, 0), 203);
+  ASSERT_TRUE(replacement_3.IsApplicable(context.get(), fact_manager));
+  replacement_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace first occurrence of %27 with %101[2] in '%28 = OpCompositeConstruct
+  // %8 %27 %27'
+  auto instruction_descriptor_4 =
+      MakeInstructionDescriptor(28, SpvOpCompositeConstruct, 0);
+  auto extract_4 =
+      TransformationCompositeExtract(instruction_descriptor_4, 204, 101, {2});
+  ASSERT_TRUE(extract_4.IsApplicable(context.get(), fact_manager));
+  extract_4.Apply(context.get(), &fact_manager);
+  auto replacement_4 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(27, instruction_descriptor_4, 0), 204);
+  ASSERT_TRUE(replacement_4.IsApplicable(context.get(), fact_manager));
+  replacement_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %22 with %102[0] in 'OpStore %23 %22'
+  auto instruction_descriptor_5 = MakeInstructionDescriptor(23, SpvOpStore, 0);
+  auto extract_5 =
+      TransformationCompositeExtract(instruction_descriptor_5, 205, 102, {0});
+  ASSERT_TRUE(extract_5.IsApplicable(context.get(), fact_manager));
+  extract_5.Apply(context.get(), &fact_manager);
+  auto replacement_5 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(22, instruction_descriptor_5, 1), 205);
+  ASSERT_TRUE(replacement_5.IsApplicable(context.get(), fact_manager));
+  replacement_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "Inner"
+               OpMemberName %9 0 "a"
+               OpMemberName %9 1 "b"
+               OpName %11 "i1"
+               OpName %17 "i2"
+               OpName %31 "Point"
+               OpMemberName %31 0 "x"
+               OpMemberName %31 1 "y"
+               OpMemberName %31 2 "z"
+               OpName %32 "Outer"
+               OpMemberName %32 0 "c"
+               OpMemberName %32 1 "d"
+               OpName %34 "o1"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeVector %7 2
+          %9 = OpTypeStruct %6 %8
+         %10 = OpTypePointer Function %9
+         %12 = OpConstant %6 1
+         %13 = OpConstant %7 2
+         %14 = OpConstant %7 3
+         %15 = OpConstantComposite %8 %13 %14
+         %16 = OpConstantComposite %9 %12 %15
+         %18 = OpConstant %6 0
+         %19 = OpTypePointer Function %6
+         %24 = OpTypePointer Function %8
+         %27 = OpConstant %7 4
+         %31 = OpTypeStruct %7 %7 %7
+         %32 = OpTypeStruct %9 %31
+         %33 = OpTypePointer Function %32
+         %36 = OpConstant %7 10
+         %37 = OpTypeInt 32 0
+         %38 = OpConstant %37 0
+         %39 = OpTypePointer Function %7
+         %42 = OpConstant %37 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+         %17 = OpVariable %10 Function
+         %34 = OpVariable %33 Function
+        %101 = OpCompositeConstruct %31 %27 %36 %27
+               OpStore %11 %16
+         %20 = OpAccessChain %19 %11 %18
+         %21 = OpLoad %6 %20
+         %22 = OpIAdd %6 %21 %12
+        %102 = OpCompositeConstruct %9 %22 %15
+         %23 = OpAccessChain %19 %17 %18
+        %205 = OpCompositeExtract %6 %102 0
+               OpStore %23 %205
+         %25 = OpAccessChain %24 %17 %12
+         %26 = OpLoad %8 %25
+        %202 = OpCompositeExtract %7 %101 0
+        %204 = OpCompositeExtract %7 %101 2
+         %28 = OpCompositeConstruct %8 %204 %202
+         %29 = OpFAdd %8 %26 %28
+         %30 = OpAccessChain %24 %17 %12
+               OpStore %30 %29
+         %35 = OpLoad %9 %11
+         %40 = OpAccessChain %39 %11 %12 %38
+         %41 = OpLoad %7 %40
+         %43 = OpAccessChain %39 %11 %12 %42
+         %44 = OpLoad %7 %43
+        %203 = OpCompositeExtract %7 %101 1
+         %45 = OpCompositeConstruct %31 %203 %41 %44
+        %100 = OpCompositeConstruct %32 %16 %45
+        %201 = OpCompositeExtract %31 %100 1
+         %46 = OpCompositeConstruct %32 %35 %201
+               OpStore %34 %46
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(DataSynonymTransformationTest, VectorCompositeSynonyms) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "f"
+               OpName %12 "v2"
+               OpName %18 "v3"
+               OpName %23 "v4"
+               OpName %32 "b"
+               OpName %36 "bv2"
+               OpName %41 "bv3"
+               OpName %50 "bv4"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 42
+         %10 = OpTypeVector %6 2
+         %11 = OpTypePointer Function %10
+         %16 = OpTypeVector %6 3
+         %17 = OpTypePointer Function %16
+         %21 = OpTypeVector %6 4
+         %22 = OpTypePointer Function %21
+         %30 = OpTypeBool
+         %31 = OpTypePointer Function %30
+         %33 = OpConstantFalse %30
+         %34 = OpTypeVector %30 2
+         %35 = OpTypePointer Function %34
+         %37 = OpConstantTrue %30
+         %38 = OpConstantComposite %34 %37 %37
+         %39 = OpTypeVector %30 3
+         %40 = OpTypePointer Function %39
+         %48 = OpTypeVector %30 4
+         %49 = OpTypePointer Function %48
+         %51 = OpTypeInt 32 0
+         %52 = OpConstant %51 2
+         %55 = OpConstant %6 0
+         %57 = OpConstant %51 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %18 = OpVariable %17 Function
+         %23 = OpVariable %22 Function
+         %32 = OpVariable %31 Function
+         %36 = OpVariable %35 Function
+         %41 = OpVariable %40 Function
+         %50 = OpVariable %49 Function
+               OpStore %8 %9
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %8
+         %15 = OpCompositeConstruct %10 %13 %14
+               OpStore %12 %15
+         %19 = OpLoad %10 %12
+         %20 = OpVectorShuffle %16 %19 %19 0 0 1
+               OpStore %18 %20
+         %24 = OpLoad %16 %18
+         %25 = OpLoad %6 %8
+         %26 = OpCompositeExtract %6 %24 0
+         %27 = OpCompositeExtract %6 %24 1
+         %28 = OpCompositeExtract %6 %24 2
+         %29 = OpCompositeConstruct %21 %26 %27 %28 %25
+               OpStore %23 %29
+               OpStore %32 %33
+               OpStore %36 %38
+         %42 = OpLoad %30 %32
+         %43 = OpLoad %34 %36
+         %44 = OpVectorShuffle %34 %43 %43 0 0
+         %45 = OpCompositeExtract %30 %44 0
+         %46 = OpCompositeExtract %30 %44 1
+         %47 = OpCompositeConstruct %39 %42 %45 %46
+               OpStore %41 %47
+         %53 = OpAccessChain %7 %23 %52
+         %54 = OpLoad %6 %53
+
+        %100 = OpCompositeConstruct %21 %20 %54
+        %101 = OpCompositeConstruct %21 %15 %19
+        %102 = OpCompositeConstruct %16 %27 %15
+        %103 = OpCompositeConstruct %48 %33 %47
+        %104 = OpCompositeConstruct %34 %42 %45
+        %105 = OpCompositeConstruct %39 %38 %46
+
+         %86 = OpCopyObject %30 %33
+         %56 = OpFOrdNotEqual %30 %54 %55
+         %80 = OpCopyObject %16 %20
+         %58 = OpAccessChain %7 %18 %57
+         %59 = OpLoad %6 %58
+         %60 = OpFOrdNotEqual %30 %59 %55
+         %61 = OpLoad %34 %36
+         %62 = OpLogicalAnd %30 %45 %46
+         %63 = OpLogicalOr %30 %45 %46
+         %64 = OpCompositeConstruct %48 %56 %60 %62 %63
+               OpStore %12 %15
+         %81 = OpVectorShuffle %16 %19 %19 0 0 1
+         %82 = OpCompositeConstruct %21 %26 %27 %28 %25
+         %83 = OpCopyObject %10 %15
+         %84 = OpCopyObject %39 %47
+               OpStore %50 %64
+         %85 = OpCopyObject %30 %42
+               OpStore %36 %38
+               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;
+  fact_manager.AddFact(MakeSynonymFact(20, {0}, 100, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(20, {1}, 100, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(20, {2}, 100, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(54, {}, 100, {3}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(15, {0}, 101, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(15, {1}, 101, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(19, {0}, 101, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(19, {1}, 101, {3}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(27, {}, 102, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(15, {0}, 102, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(15, {1}, 102, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(33, {}, 103, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(47, {0}, 103, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(47, {1}, 103, {2}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(47, {2}, 103, {3}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(42, {}, 104, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(45, {}, 104, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(38, {0}, 105, {0}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(38, {1}, 105, {1}), context.get());
+  fact_manager.AddFact(MakeSynonymFact(46, {}, 105, {2}), context.get());
+
+  // Replace %20 with %100[0:2] in '%80 = OpCopyObject %16 %20'
+  auto instruction_descriptor_1 =
+      MakeInstructionDescriptor(80, SpvOpCopyObject, 0);
+  auto shuffle_1 = TransformationVectorShuffle(instruction_descriptor_1, 200,
+                                               100, 100, {0, 1, 2});
+  ASSERT_TRUE(shuffle_1.IsApplicable(context.get(), fact_manager));
+  shuffle_1.Apply(context.get(), &fact_manager);
+  auto replacement_1 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(20, instruction_descriptor_1, 0), 200);
+  ASSERT_TRUE(replacement_1.IsApplicable(context.get(), fact_manager));
+  replacement_1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %54 with %100[3] in '%56 = OpFOrdNotEqual %30 %54 %55'
+  auto instruction_descriptor_2 =
+      MakeInstructionDescriptor(56, SpvOpFOrdNotEqual, 0);
+  auto extract_2 =
+      TransformationCompositeExtract(instruction_descriptor_2, 201, 100, {3});
+
+  ASSERT_TRUE(extract_2.IsApplicable(context.get(), fact_manager));
+  extract_2.Apply(context.get(), &fact_manager);
+  auto replacement_2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(54, instruction_descriptor_2, 0), 201);
+  ASSERT_TRUE(replacement_2.IsApplicable(context.get(), fact_manager));
+  replacement_2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %15 with %101[0:1] in 'OpStore %12 %15'
+  auto instruction_descriptor_3 = MakeInstructionDescriptor(64, SpvOpStore, 0);
+  auto shuffle_3 = TransformationVectorShuffle(instruction_descriptor_3, 202,
+                                               101, 101, {0, 1});
+  ASSERT_TRUE(shuffle_3.IsApplicable(context.get(), fact_manager));
+  shuffle_3.Apply(context.get(), &fact_manager);
+  auto replacement_3 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(15, instruction_descriptor_3, 1), 202);
+  ASSERT_TRUE(replacement_3.IsApplicable(context.get(), fact_manager));
+  replacement_3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %19 with %101[2:3] in '%81 = OpVectorShuffle %16 %19 %19 0 0 1'
+  auto instruction_descriptor_4 =
+      MakeInstructionDescriptor(81, SpvOpVectorShuffle, 0);
+  auto shuffle_4 = TransformationVectorShuffle(instruction_descriptor_4, 203,
+                                               101, 101, {2, 3});
+  ASSERT_TRUE(shuffle_4.IsApplicable(context.get(), fact_manager));
+  shuffle_4.Apply(context.get(), &fact_manager);
+  auto replacement_4 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(19, instruction_descriptor_4, 0), 203);
+  ASSERT_TRUE(replacement_4.IsApplicable(context.get(), fact_manager));
+  replacement_4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %27 with %102[0] in '%82 = OpCompositeConstruct %21 %26 %27 %28
+  // %25'
+  auto instruction_descriptor_5 =
+      MakeInstructionDescriptor(82, SpvOpCompositeConstruct, 0);
+  auto extract_5 =
+      TransformationCompositeExtract(instruction_descriptor_5, 204, 102, {0});
+
+  ASSERT_TRUE(extract_5.IsApplicable(context.get(), fact_manager));
+  extract_5.Apply(context.get(), &fact_manager);
+  auto replacement_5 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(27, instruction_descriptor_5, 1), 204);
+  ASSERT_TRUE(replacement_5.IsApplicable(context.get(), fact_manager));
+  replacement_5.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %15 with %102[1:2] in '%83 = OpCopyObject %10 %15'
+  auto instruction_descriptor_6 =
+      MakeInstructionDescriptor(83, SpvOpCopyObject, 0);
+  auto shuffle_6 = TransformationVectorShuffle(instruction_descriptor_6, 205,
+                                               102, 102, {1, 2});
+  ASSERT_TRUE(shuffle_6.IsApplicable(context.get(), fact_manager));
+  shuffle_6.Apply(context.get(), &fact_manager);
+  auto replacement_6 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(15, instruction_descriptor_6, 0), 205);
+  ASSERT_TRUE(replacement_6.IsApplicable(context.get(), fact_manager));
+  replacement_6.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %33 with %103[0] in '%86 = OpCopyObject %30 %33'
+  auto instruction_descriptor_7 =
+      MakeInstructionDescriptor(86, SpvOpCopyObject, 0);
+  auto extract_7 =
+      TransformationCompositeExtract(instruction_descriptor_7, 206, 103, {0});
+  ASSERT_TRUE(extract_7.IsApplicable(context.get(), fact_manager));
+  extract_7.Apply(context.get(), &fact_manager);
+  auto replacement_7 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(33, instruction_descriptor_7, 0), 206);
+  ASSERT_TRUE(replacement_7.IsApplicable(context.get(), fact_manager));
+  replacement_7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %47 with %103[1:3] in '%84 = OpCopyObject %39 %47'
+  auto instruction_descriptor_8 =
+      MakeInstructionDescriptor(84, SpvOpCopyObject, 0);
+  auto shuffle_8 = TransformationVectorShuffle(instruction_descriptor_8, 207,
+                                               103, 103, {1, 2, 3});
+  ASSERT_TRUE(shuffle_8.IsApplicable(context.get(), fact_manager));
+  shuffle_8.Apply(context.get(), &fact_manager);
+  auto replacement_8 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(47, instruction_descriptor_8, 0), 207);
+  ASSERT_TRUE(replacement_8.IsApplicable(context.get(), fact_manager));
+  replacement_8.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %42 with %104[0] in '%85 = OpCopyObject %30 %42'
+  auto instruction_descriptor_9 =
+      MakeInstructionDescriptor(85, SpvOpCopyObject, 0);
+  auto extract_9 =
+      TransformationCompositeExtract(instruction_descriptor_9, 208, 104, {0});
+  ASSERT_TRUE(extract_9.IsApplicable(context.get(), fact_manager));
+  extract_9.Apply(context.get(), &fact_manager);
+  auto replacement_9 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(42, instruction_descriptor_9, 0), 208);
+  ASSERT_TRUE(replacement_9.IsApplicable(context.get(), fact_manager));
+  replacement_9.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %45 with %104[1] in '%63 = OpLogicalOr %30 %45 %46'
+  auto instruction_descriptor_10 =
+      MakeInstructionDescriptor(63, SpvOpLogicalOr, 0);
+  auto extract_10 =
+      TransformationCompositeExtract(instruction_descriptor_10, 209, 104, {1});
+  ASSERT_TRUE(extract_10.IsApplicable(context.get(), fact_manager));
+  extract_10.Apply(context.get(), &fact_manager);
+  auto replacement_10 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(45, instruction_descriptor_10, 0), 209);
+  ASSERT_TRUE(replacement_10.IsApplicable(context.get(), fact_manager));
+  replacement_10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %38 with %105[0:1] in 'OpStore %36 %38'
+  auto instruction_descriptor_11 = MakeInstructionDescriptor(85, SpvOpStore, 0);
+  auto shuffle_11 = TransformationVectorShuffle(instruction_descriptor_11, 210,
+                                                105, 105, {0, 1});
+  ASSERT_TRUE(shuffle_11.IsApplicable(context.get(), fact_manager));
+  shuffle_11.Apply(context.get(), &fact_manager);
+  auto replacement_11 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(38, instruction_descriptor_11, 1), 210);
+  ASSERT_TRUE(replacement_11.IsApplicable(context.get(), fact_manager));
+  replacement_11.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %46 with %105[2] in '%62 = OpLogicalAnd %30 %45 %46'
+  auto instruction_descriptor_12 =
+      MakeInstructionDescriptor(62, SpvOpLogicalAnd, 0);
+  auto extract_12 =
+      TransformationCompositeExtract(instruction_descriptor_12, 211, 105, {2});
+  ASSERT_TRUE(extract_12.IsApplicable(context.get(), fact_manager));
+  extract_12.Apply(context.get(), &fact_manager);
+  auto replacement_12 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(46, instruction_descriptor_12, 1), 211);
+  ASSERT_TRUE(replacement_12.IsApplicable(context.get(), fact_manager));
+  replacement_12.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "f"
+               OpName %12 "v2"
+               OpName %18 "v3"
+               OpName %23 "v4"
+               OpName %32 "b"
+               OpName %36 "bv2"
+               OpName %41 "bv3"
+               OpName %50 "bv4"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 42
+         %10 = OpTypeVector %6 2
+         %11 = OpTypePointer Function %10
+         %16 = OpTypeVector %6 3
+         %17 = OpTypePointer Function %16
+         %21 = OpTypeVector %6 4
+         %22 = OpTypePointer Function %21
+         %30 = OpTypeBool
+         %31 = OpTypePointer Function %30
+         %33 = OpConstantFalse %30
+         %34 = OpTypeVector %30 2
+         %35 = OpTypePointer Function %34
+         %37 = OpConstantTrue %30
+         %38 = OpConstantComposite %34 %37 %37
+         %39 = OpTypeVector %30 3
+         %40 = OpTypePointer Function %39
+         %48 = OpTypeVector %30 4
+         %49 = OpTypePointer Function %48
+         %51 = OpTypeInt 32 0
+         %52 = OpConstant %51 2
+         %55 = OpConstant %6 0
+         %57 = OpConstant %51 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %12 = OpVariable %11 Function
+         %18 = OpVariable %17 Function
+         %23 = OpVariable %22 Function
+         %32 = OpVariable %31 Function
+         %36 = OpVariable %35 Function
+         %41 = OpVariable %40 Function
+         %50 = OpVariable %49 Function
+               OpStore %8 %9
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %8
+         %15 = OpCompositeConstruct %10 %13 %14
+               OpStore %12 %15
+         %19 = OpLoad %10 %12
+         %20 = OpVectorShuffle %16 %19 %19 0 0 1
+               OpStore %18 %20
+         %24 = OpLoad %16 %18
+         %25 = OpLoad %6 %8
+         %26 = OpCompositeExtract %6 %24 0
+         %27 = OpCompositeExtract %6 %24 1
+         %28 = OpCompositeExtract %6 %24 2
+         %29 = OpCompositeConstruct %21 %26 %27 %28 %25
+               OpStore %23 %29
+               OpStore %32 %33
+               OpStore %36 %38
+         %42 = OpLoad %30 %32
+         %43 = OpLoad %34 %36
+         %44 = OpVectorShuffle %34 %43 %43 0 0
+         %45 = OpCompositeExtract %30 %44 0
+         %46 = OpCompositeExtract %30 %44 1
+         %47 = OpCompositeConstruct %39 %42 %45 %46
+               OpStore %41 %47
+         %53 = OpAccessChain %7 %23 %52
+         %54 = OpLoad %6 %53
+
+        %100 = OpCompositeConstruct %21 %20 %54
+        %101 = OpCompositeConstruct %21 %15 %19
+        %102 = OpCompositeConstruct %16 %27 %15
+        %103 = OpCompositeConstruct %48 %33 %47
+        %104 = OpCompositeConstruct %34 %42 %45
+        %105 = OpCompositeConstruct %39 %38 %46
+
+        %206 = OpCompositeExtract %30 %103 0
+         %86 = OpCopyObject %30 %206
+        %201 = OpCompositeExtract %6 %100 3
+         %56 = OpFOrdNotEqual %30 %201 %55
+        %200 = OpVectorShuffle %16 %100 %100 0 1 2
+         %80 = OpCopyObject %16 %200
+         %58 = OpAccessChain %7 %18 %57
+         %59 = OpLoad %6 %58
+         %60 = OpFOrdNotEqual %30 %59 %55
+         %61 = OpLoad %34 %36
+        %211 = OpCompositeExtract %30 %105 2
+         %62 = OpLogicalAnd %30 %45 %211
+        %209 = OpCompositeExtract %30 %104 1
+         %63 = OpLogicalOr %30 %209 %46
+         %64 = OpCompositeConstruct %48 %56 %60 %62 %63
+        %202 = OpVectorShuffle %10 %101 %101 0 1
+               OpStore %12 %202
+        %203 = OpVectorShuffle %10 %101 %101 2 3
+         %81 = OpVectorShuffle %16 %203 %19 0 0 1
+        %204 = OpCompositeExtract %6 %102 0
+         %82 = OpCompositeConstruct %21 %26 %204 %28 %25
+        %205 = OpVectorShuffle %10 %102 %102 1 2
+         %83 = OpCopyObject %10 %205
+        %207 = OpVectorShuffle %39 %103 %103 1 2 3
+         %84 = OpCopyObject %39 %207
+               OpStore %50 %64
+        %208 = OpCompositeExtract %30 %104 0
+         %85 = OpCopyObject %30 %208
+        %210 = OpVectorShuffle %34 %105 %105 0 1
+               OpStore %36 %210
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools

+ 22 - 0
3rdparty/spirv-tools/test/fuzz/equivalence_relation_test.cpp

@@ -118,6 +118,28 @@ TEST(EquivalenceRelationTest, BasicTest) {
   }
 }
 
+TEST(EquivalenceRelationTest, DeterministicEquivalenceClassOrder) {
+  EquivalenceRelation<uint32_t, UInt32Hash, UInt32Equals> relation1;
+  EquivalenceRelation<uint32_t, UInt32Hash, UInt32Equals> relation2;
+
+  for (uint32_t i = 0; i < 1000; ++i) {
+    if (i >= 10) {
+      relation1.MakeEquivalent(i, i - 10);
+      relation2.MakeEquivalent(i, i - 10);
+    }
+  }
+
+  // We constructed the equivalence relations in the same way, so we would like
+  // them to have identical representatives, and identically-ordered equivalence
+  // classes per representative.
+  ASSERT_THAT(ToUIntVector(relation1.GetEquivalenceClassRepresentatives()),
+              ToUIntVector(relation2.GetEquivalenceClassRepresentatives()));
+  for (auto representative : relation1.GetEquivalenceClassRepresentatives()) {
+    ASSERT_THAT(ToUIntVector(relation1.GetEquivalenceClass(*representative)),
+                ToUIntVector(relation2.GetEquivalenceClass(*representative)));
+  }
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools

+ 435 - 0
3rdparty/spirv-tools/test/fuzz/fact_manager_test.cpp

@@ -738,6 +738,441 @@ TEST(FactManagerTest, AmbiguousFact) {
                              uniform_buffer_element_descriptor));
 }
 
+TEST(FactManagerTest, DataSynonymFacts) {
+  // The SPIR-V types and constants come from the following code.  The body of
+  // the SPIR-V function then constructs a composite that is synonymous with
+  // myT.
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // struct S {
+  //   int a;
+  //   uvec2 b;
+  // };
+  //
+  // struct T {
+  //   bool c[5];
+  //   mat4x2 d;
+  //   S e;
+  // };
+  //
+  // void main() {
+  //   T myT = T(bool[5](true, false, true, false, true),
+  //             mat4x2(vec2(1.0, 2.0), vec2(3.0, 4.0),
+  // 	           vec2(5.0, 6.0), vec2(7.0, 8.0)),
+  //             S(10, uvec2(100u, 200u)));
+  // }
+
+  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 %15 "S"
+               OpMemberName %15 0 "a"
+               OpMemberName %15 1 "b"
+               OpName %16 "T"
+               OpMemberName %16 0 "c"
+               OpMemberName %16 1 "d"
+               OpMemberName %16 2 "e"
+               OpName %18 "myT"
+               OpMemberDecorate %15 0 RelaxedPrecision
+               OpMemberDecorate %15 1 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 5
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeFloat 32
+         %11 = OpTypeVector %10 2
+         %12 = OpTypeMatrix %11 4
+         %13 = OpTypeInt 32 1
+         %14 = OpTypeVector %7 2
+         %15 = OpTypeStruct %13 %14
+         %16 = OpTypeStruct %9 %12 %15
+         %17 = OpTypePointer Function %16
+         %19 = OpConstantTrue %6
+         %20 = OpConstantFalse %6
+         %21 = OpConstantComposite %9 %19 %20 %19 %20 %19
+         %22 = OpConstant %10 1
+         %23 = OpConstant %10 2
+         %24 = OpConstantComposite %11 %22 %23
+         %25 = OpConstant %10 3
+         %26 = OpConstant %10 4
+         %27 = OpConstantComposite %11 %25 %26
+         %28 = OpConstant %10 5
+         %29 = OpConstant %10 6
+         %30 = OpConstantComposite %11 %28 %29
+         %31 = OpConstant %10 7
+         %32 = OpConstant %10 8
+         %33 = OpConstantComposite %11 %31 %32
+         %34 = OpConstantComposite %12 %24 %27 %30 %33
+         %35 = OpConstant %13 10
+         %36 = OpConstant %7 100
+         %37 = OpConstant %7 200
+         %38 = OpConstantComposite %14 %36 %37
+         %39 = OpConstantComposite %15 %35 %38
+         %40 = OpConstantComposite %16 %21 %34 %39
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpVariable %17 Function
+               OpStore %18 %40
+        %100 = OpCompositeConstruct %9 %19 %20 %19 %20 %19
+        %101 = OpCompositeConstruct %11 %22 %23
+        %102 = OpCompositeConstruct %11 %25 %26
+        %103 = OpCompositeConstruct %11 %28 %29
+        %104 = OpCompositeConstruct %11 %31 %32
+        %105 = OpCompositeConstruct %12 %101 %102 %103 %104
+        %106 = OpCompositeConstruct %14 %36 %37
+        %107 = OpCompositeConstruct %15 %35 %106
+        %108 = OpCompositeConstruct %16 %100 %105 %107
+               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;
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(101, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
+                                         MakeDataDescriptor(101, {0}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {1}),
+                                         MakeDataDescriptor(101, {1}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
+                                         MakeDataDescriptor(101, {1}),
+                                         context.get()));
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(24, {}),
+                                  MakeDataDescriptor(101, {}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(101, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
+                                        MakeDataDescriptor(101, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {1}),
+                                        MakeDataDescriptor(101, {1}),
+                                        context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
+                                         MakeDataDescriptor(101, {1}),
+                                         context.get()));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
+                                         MakeDataDescriptor(102, {0}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
+                                         MakeDataDescriptor(102, {1}),
+                                         context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {0}),
+                                  MakeDataDescriptor(102, {0}), context.get());
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
+                                        MakeDataDescriptor(102, {0}),
+                                        context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
+                                         MakeDataDescriptor(102, {1}),
+                                         context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {1}),
+                                  MakeDataDescriptor(102, {1}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
+                                        MakeDataDescriptor(102, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
+                                        MakeDataDescriptor(102, {1}),
+                                        context.get()));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(30, {}), MakeDataDescriptor(103, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
+                                         MakeDataDescriptor(103, {0}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
+                                         MakeDataDescriptor(103, {1}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(33, {}), MakeDataDescriptor(104, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
+                                         MakeDataDescriptor(104, {0}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {1}),
+                                         MakeDataDescriptor(104, {1}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(34, {}), MakeDataDescriptor(105, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {0}),
+                                         MakeDataDescriptor(105, {0}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {1}),
+                                         MakeDataDescriptor(105, {1}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {2}),
+                                         MakeDataDescriptor(105, {2}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
+                                         MakeDataDescriptor(105, {3}),
+                                         context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(30, {}),
+                                  MakeDataDescriptor(103, {}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(33, {}),
+                                  MakeDataDescriptor(104, {}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {0}),
+                                  MakeDataDescriptor(105, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {1}),
+                                  MakeDataDescriptor(105, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {2}),
+                                  MakeDataDescriptor(105, {2}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(30, {}), MakeDataDescriptor(103, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
+                                        MakeDataDescriptor(103, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
+                                        MakeDataDescriptor(103, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(33, {}), MakeDataDescriptor(104, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
+                                        MakeDataDescriptor(104, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {1}),
+                                        MakeDataDescriptor(104, {1}),
+                                        context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(34, {}), MakeDataDescriptor(105, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {0}),
+                                        MakeDataDescriptor(105, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {1}),
+                                        MakeDataDescriptor(105, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {2}),
+                                        MakeDataDescriptor(105, {2}),
+                                        context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
+                                         MakeDataDescriptor(105, {3}),
+                                         context.get()));
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {3}),
+                                  MakeDataDescriptor(105, {3}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
+                                        MakeDataDescriptor(104, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
+                                        MakeDataDescriptor(105, {3}),
+                                        context.get()));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(100, {}), context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {0}),
+                                  MakeDataDescriptor(100, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {1}),
+                                  MakeDataDescriptor(100, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {2}),
+                                  MakeDataDescriptor(100, {2}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {3}),
+                                  MakeDataDescriptor(100, {3}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {4}),
+                                  MakeDataDescriptor(100, {4}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(100, {}), context.get()));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}),
+                                         MakeDataDescriptor(107, {0}),
+                                         context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(35, {}), MakeDataDescriptor(39, {0}), context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(39, {0}),
+                                  MakeDataDescriptor(35, {}), context.get());
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}),
+                                         MakeDataDescriptor(107, {0}),
+                                         context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(35, {}), MakeDataDescriptor(39, {0}), context.get()));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {0}), MakeDataDescriptor(36, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {1}), MakeDataDescriptor(37, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(106, {0}), MakeDataDescriptor(36, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(106, {1}), MakeDataDescriptor(37, {}), context.get()));
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(106, {}), context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {0}),
+                                  MakeDataDescriptor(36, {}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {0}),
+                                  MakeDataDescriptor(36, {}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {1}),
+                                  MakeDataDescriptor(37, {}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {1}),
+                                  MakeDataDescriptor(37, {}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {0}), MakeDataDescriptor(36, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {1}), MakeDataDescriptor(37, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(106, {0}), MakeDataDescriptor(36, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(106, {1}), MakeDataDescriptor(37, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(106, {}), context.get()));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(108, {}), context.get()));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(107, {0}),
+                                  MakeDataDescriptor(35, {}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {0}),
+                                  MakeDataDescriptor(108, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {1}),
+                                  MakeDataDescriptor(108, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {2}),
+                                  MakeDataDescriptor(108, {2}), context.get());
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(108, {}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0}),
+                                        MakeDataDescriptor(108, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1}),
+                                        MakeDataDescriptor(108, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2}),
+                                        MakeDataDescriptor(108, {2}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 0}),
+                                        MakeDataDescriptor(108, {0, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 1}),
+                                        MakeDataDescriptor(108, {0, 1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 2}),
+                                        MakeDataDescriptor(108, {0, 2}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 3}),
+                                        MakeDataDescriptor(108, {0, 3}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 4}),
+                                        MakeDataDescriptor(108, {0, 4}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0}),
+                                        MakeDataDescriptor(108, {1, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1}),
+                                        MakeDataDescriptor(108, {1, 1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2}),
+                                        MakeDataDescriptor(108, {1, 2}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3}),
+                                        MakeDataDescriptor(108, {1, 3}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0, 0}),
+                                        MakeDataDescriptor(108, {1, 0, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1, 0}),
+                                        MakeDataDescriptor(108, {1, 1, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2, 0}),
+                                        MakeDataDescriptor(108, {1, 2, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3, 0}),
+                                        MakeDataDescriptor(108, {1, 3, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0, 1}),
+                                        MakeDataDescriptor(108, {1, 0, 1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1, 1}),
+                                        MakeDataDescriptor(108, {1, 1, 1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2, 1}),
+                                        MakeDataDescriptor(108, {1, 2, 1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3, 1}),
+                                        MakeDataDescriptor(108, {1, 3, 1}),
+                                        context.get()));
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 0}),
+                                        MakeDataDescriptor(108, {2, 0}),
+                                        context.get()));
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1}),
+                                        MakeDataDescriptor(108, {2, 1}),
+                                        context.get()));
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1, 0}),
+                                        MakeDataDescriptor(108, {2, 1, 0}),
+                                        context.get()));
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1, 1}),
+                                        MakeDataDescriptor(108, {2, 1, 1}),
+                                        context.get()));
+}
+
+TEST(FactManagerTest, RecursiveAdditionOfFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypeMatrix %7 4
+          %9 = OpConstant %6 0
+         %10 = OpConstantComposite %7 %9 %9 %9 %9
+         %11 = OpConstantComposite %8 %10 %10 %10 %10
+         %12 = OpFunction %2 None %3
+         %13 = 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;
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(11, {2}), context.get());
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(11, {2}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {0}),
+                                        MakeDataDescriptor(11, {2, 0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {1}),
+                                        MakeDataDescriptor(11, {2, 1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {2}),
+                                        MakeDataDescriptor(11, {2, 2}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {3}),
+                                        MakeDataDescriptor(11, {2, 3}),
+                                        context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools

+ 116 - 95
3rdparty/spirv-tools/test/fuzz/transformation_composite_construct_test.cpp

@@ -144,12 +144,12 @@ TEST(TransformationCompositeConstructTest, ConstructArrays) {
       make_vec2_array_length_3_bad.IsApplicable(context.get(), fact_manager));
   make_vec2_array_length_3.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}),
-                                        MakeDataDescriptor(200, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(45, {}),
-                                        MakeDataDescriptor(200, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
-                                        MakeDataDescriptor(200, {2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(200, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(45, {}), MakeDataDescriptor(200, {1}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(200, {2}), context.get()));
 
   // Make a float[2]
   TransformationCompositeConstruct make_float_array_length_2(
@@ -163,10 +163,10 @@ TEST(TransformationCompositeConstructTest, ConstructArrays) {
       make_float_array_length_2_bad.IsApplicable(context.get(), fact_manager));
   make_float_array_length_2.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
-                                        MakeDataDescriptor(201, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}),
-                                        MakeDataDescriptor(201, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(201, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(201, {1}), context.get()));
 
   // Make a bool[3]
   TransformationCompositeConstruct make_bool_array_length_3(
@@ -182,12 +182,12 @@ TEST(TransformationCompositeConstructTest, ConstructArrays) {
       make_bool_array_length_3_bad.IsApplicable(context.get(), fact_manager));
   make_bool_array_length_3.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
-                                        MakeDataDescriptor(202, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {}),
-                                        MakeDataDescriptor(202, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(50, {}),
-                                        MakeDataDescriptor(202, {2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(33, {}), MakeDataDescriptor(202, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(50, {}), MakeDataDescriptor(202, {1}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(50, {}), MakeDataDescriptor(202, {2}), context.get()));
 
   // make a uvec3[2][2]
   TransformationCompositeConstruct make_uvec3_array_length_2_2(
@@ -201,10 +201,11 @@ TEST(TransformationCompositeConstructTest, ConstructArrays) {
                                                             fact_manager));
   make_uvec3_array_length_2_2.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(69, {}),
-                                        MakeDataDescriptor(203, {0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(69, {}), MakeDataDescriptor(203, {0}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}),
-                                        MakeDataDescriptor(203, {1})));
+                                        MakeDataDescriptor(203, {1}),
+                                        context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -403,12 +404,12 @@ TEST(TransformationCompositeConstructTest, ConstructMatrices) {
   ASSERT_FALSE(make_mat34_bad.IsApplicable(context.get(), fact_manager));
   make_mat34.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
-                                        MakeDataDescriptor(200, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}),
-                                        MakeDataDescriptor(200, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(31, {}),
-                                        MakeDataDescriptor(200, {2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(28, {}), MakeDataDescriptor(200, {1}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(31, {}), MakeDataDescriptor(200, {2}), context.get()));
 
   // make a mat4x3
   TransformationCompositeConstruct make_mat43(
@@ -420,14 +421,15 @@ TEST(TransformationCompositeConstructTest, ConstructMatrices) {
   ASSERT_FALSE(make_mat43_bad.IsApplicable(context.get(), fact_manager));
   make_mat43.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}),
-                                        MakeDataDescriptor(201, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(13, {}),
-                                        MakeDataDescriptor(201, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(16, {}),
-                                        MakeDataDescriptor(201, {2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(201, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(13, {}), MakeDataDescriptor(201, {1}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(16, {}), MakeDataDescriptor(201, {2}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(100, {}),
-                                        MakeDataDescriptor(201, {3})));
+                                        MakeDataDescriptor(201, {3}),
+                                        context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -611,10 +613,10 @@ TEST(TransformationCompositeConstructTest, ConstructStructs) {
   ASSERT_FALSE(make_inner_bad.IsApplicable(context.get(), fact_manager));
   make_inner.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
-                                        MakeDataDescriptor(200, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(19, {}),
-                                        MakeDataDescriptor(200, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(19, {}), MakeDataDescriptor(200, {1}), context.get()));
 
   // make an Outer
   TransformationCompositeConstruct make_outer(
@@ -628,12 +630,13 @@ TEST(TransformationCompositeConstructTest, ConstructStructs) {
   ASSERT_FALSE(make_outer_bad.IsApplicable(context.get(), fact_manager));
   make_outer.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(46, {}),
-                                        MakeDataDescriptor(201, {0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(46, {}), MakeDataDescriptor(201, {0}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(200, {}),
-                                        MakeDataDescriptor(201, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(56, {}),
-                                        MakeDataDescriptor(201, {2})));
+                                        MakeDataDescriptor(201, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(201, {2}), context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -929,10 +932,10 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_vec2_bad.IsApplicable(context.get(), fact_manager));
   make_vec2.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
-                                        MakeDataDescriptor(200, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}),
-                                        MakeDataDescriptor(200, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(200, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(200, {1}), context.get()));
 
   TransformationCompositeConstruct make_vec3(
       25, {12, 32}, MakeInstructionDescriptor(35, SpvOpCompositeConstruct, 0),
@@ -946,11 +949,13 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   make_vec3.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {0}),
-                                        MakeDataDescriptor(201, {0})));
+                                        MakeDataDescriptor(201, {0}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {1}),
-                                        MakeDataDescriptor(201, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                        MakeDataDescriptor(201, {2})));
+                                        MakeDataDescriptor(201, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(32, {}), MakeDataDescriptor(201, {2}), context.get()));
 
   TransformationCompositeConstruct make_vec4(
       44, {32, 32, 10, 11}, MakeInstructionDescriptor(75, SpvOpAccessChain, 0),
@@ -963,14 +968,14 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_vec4_bad.IsApplicable(context.get(), fact_manager));
   make_vec4.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                        MakeDataDescriptor(202, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                        MakeDataDescriptor(202, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}),
-                                        MakeDataDescriptor(202, {2})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}),
-                                        MakeDataDescriptor(202, {3})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(32, {}), MakeDataDescriptor(202, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(32, {}), MakeDataDescriptor(202, {1}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(202, {2}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(202, {3}), context.get()));
 
   TransformationCompositeConstruct make_ivec2(
       51, {126, 120}, MakeInstructionDescriptor(128, SpvOpLoad, 0), 203);
@@ -982,9 +987,11 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   make_ivec2.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(126, {}),
-                                        MakeDataDescriptor(203, {0})));
+                                        MakeDataDescriptor(203, {0}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(120, {}),
-                                        MakeDataDescriptor(203, {1})));
+                                        MakeDataDescriptor(203, {1}),
+                                        context.get()));
 
   TransformationCompositeConstruct make_ivec3(
       114, {56, 117, 56}, MakeInstructionDescriptor(66, SpvOpAccessChain, 0),
@@ -997,12 +1004,13 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_ivec3_bad.IsApplicable(context.get(), fact_manager));
   make_ivec3.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(56, {}),
-                                        MakeDataDescriptor(204, {0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(204, {0}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(204, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(56, {}),
-                                        MakeDataDescriptor(204, {2})));
+                                        MakeDataDescriptor(204, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(204, {2}), context.get()));
 
   TransformationCompositeConstruct make_ivec4(
       122, {56, 117, 117, 117}, MakeInstructionDescriptor(66, SpvOpIAdd, 0),
@@ -1015,14 +1023,17 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_ivec4_bad.IsApplicable(context.get(), fact_manager));
   make_ivec4.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(56, {}),
-                                        MakeDataDescriptor(205, {0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(205, {0}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(205, {1})));
+                                        MakeDataDescriptor(205, {1}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(205, {2})));
+                                        MakeDataDescriptor(205, {2}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(117, {}),
-                                        MakeDataDescriptor(205, {3})));
+                                        MakeDataDescriptor(205, {3}),
+                                        context.get()));
 
   TransformationCompositeConstruct make_uvec2(
       86, {18, 38}, MakeInstructionDescriptor(133, SpvOpAccessChain, 0), 206);
@@ -1032,10 +1043,10 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_uvec2_bad.IsApplicable(context.get(), fact_manager));
   make_uvec2.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
-                                        MakeDataDescriptor(206, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {}),
-                                        MakeDataDescriptor(206, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(206, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(206, {1}), context.get()));
 
   TransformationCompositeConstruct make_uvec3(
       59, {14, 18, 136}, MakeInstructionDescriptor(137, SpvOpReturn, 0), 207);
@@ -1046,12 +1057,13 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_uvec3_bad.IsApplicable(context.get(), fact_manager));
   make_uvec3.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(14, {}),
-                                        MakeDataDescriptor(207, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
-                                        MakeDataDescriptor(207, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(14, {}), MakeDataDescriptor(207, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(207, {1}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(136, {}),
-                                        MakeDataDescriptor(207, {2})));
+                                        MakeDataDescriptor(207, {2}),
+                                        context.get()));
 
   TransformationCompositeConstruct make_uvec4(
       131, {14, 18, 136, 136},
@@ -1064,14 +1076,16 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   ASSERT_FALSE(make_uvec4_bad.IsApplicable(context.get(), fact_manager));
   make_uvec4.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(14, {}),
-                                        MakeDataDescriptor(208, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
-                                        MakeDataDescriptor(208, {1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(14, {}), MakeDataDescriptor(208, {0}), context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(208, {1}), context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(136, {}),
-                                        MakeDataDescriptor(208, {2})));
+                                        MakeDataDescriptor(208, {2}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(136, {}),
-                                        MakeDataDescriptor(208, {3})));
+                                        MakeDataDescriptor(208, {3}),
+                                        context.get()));
 
   TransformationCompositeConstruct make_bvec2(
       102,
@@ -1093,9 +1107,10 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   make_bvec2.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(111, {}),
-                                        MakeDataDescriptor(209, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}),
-                                        MakeDataDescriptor(209, {1})));
+                                        MakeDataDescriptor(209, {0}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(209, {1}), context.get()));
 
   TransformationCompositeConstruct make_bvec3(
       93, {108, 73}, MakeInstructionDescriptor(108, SpvOpStore, 0), 210);
@@ -1107,11 +1122,13 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   make_bvec3.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {0}),
-                                        MakeDataDescriptor(210, {0})));
+                                        MakeDataDescriptor(210, {0}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {1}),
-                                        MakeDataDescriptor(210, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(73, {}),
-                                        MakeDataDescriptor(210, {2})));
+                                        MakeDataDescriptor(210, {1}),
+                                        context.get()));
+  ASSERT_TRUE(fact_manager.IsSynonymous(
+      MakeDataDescriptor(73, {}), MakeDataDescriptor(210, {2}), context.get()));
 
   TransformationCompositeConstruct make_bvec4(
       70, {108, 108}, MakeInstructionDescriptor(108, SpvOpBranch, 0), 211);
@@ -1123,13 +1140,17 @@ TEST(TransformationCompositeConstructTest, ConstructVectors) {
   make_bvec4.Apply(context.get(), &fact_manager);
   ASSERT_TRUE(IsValid(env, context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {0}),
-                                        MakeDataDescriptor(211, {0})));
+                                        MakeDataDescriptor(211, {0}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {1}),
-                                        MakeDataDescriptor(211, {1})));
+                                        MakeDataDescriptor(211, {1}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {0}),
-                                        MakeDataDescriptor(211, {2})));
+                                        MakeDataDescriptor(211, {2}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(108, {1}),
-                                        MakeDataDescriptor(211, {3})));
+                                        MakeDataDescriptor(211, {3}),
+                                        context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader

+ 12 - 6
3rdparty/spirv-tools/test/fuzz/transformation_composite_extract_test.cpp

@@ -173,17 +173,23 @@ TEST(TransformationCompositeExtractTest, BasicTest) {
   ASSERT_TRUE(IsValid(env, context.get()));
 
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(201, {}),
-                                        MakeDataDescriptor(100, {2})));
+                                        MakeDataDescriptor(100, {2}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(202, {}),
-                                        MakeDataDescriptor(104, {0, 2})));
+                                        MakeDataDescriptor(104, {0, 2}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(203, {}),
-                                        MakeDataDescriptor(104, {0})));
+                                        MakeDataDescriptor(104, {0}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(204, {}),
-                                        MakeDataDescriptor(101, {0})));
+                                        MakeDataDescriptor(101, {0}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(205, {}),
-                                        MakeDataDescriptor(102, {2})));
+                                        MakeDataDescriptor(102, {2}),
+                                        context.get()));
   ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(206, {}),
-                                        MakeDataDescriptor(103, {1})));
+                                        MakeDataDescriptor(103, {1}),
+                                        context.get()));
 
   std::string after_transformation = R"(
                OpCapability Shader

+ 22 - 21
3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp

@@ -52,7 +52,8 @@ TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
 
   FactManager fact_manager;
 
-  ASSERT_EQ(0, fact_manager.GetIdsForWhichSynonymsAreKnown().size());
+  ASSERT_EQ(0,
+            fact_manager.GetIdsForWhichSynonymsAreKnown(context.get()).size());
 
   {
     TransformationCopyObject copy_true(
@@ -60,16 +61,16 @@ TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
     ASSERT_TRUE(copy_true.IsApplicable(context.get(), fact_manager));
     copy_true.Apply(context.get(), &fact_manager);
 
-    auto ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown();
+    std::vector<uint32_t> ids_for_which_synonyms_are_known =
+        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
     ASSERT_EQ(2, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           7) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(2, fact_manager.GetSynonymsForId(7).size());
+    ASSERT_EQ(2, fact_manager.GetSynonymsForId(7, context.get()).size());
     protobufs::DataDescriptor descriptor_100 = MakeDataDescriptor(100, {});
-    ASSERT_TRUE(
-        fact_manager.IsSynonymous(MakeDataDescriptor(7, {}), descriptor_100));
+    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
+                                          descriptor_100, context.get()));
   }
 
   {
@@ -77,16 +78,16 @@ TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
         8, MakeInstructionDescriptor(100, SpvOpReturn, 0), 101);
     ASSERT_TRUE(copy_false.IsApplicable(context.get(), fact_manager));
     copy_false.Apply(context.get(), &fact_manager);
-    auto ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown();
+    std::vector<uint32_t> ids_for_which_synonyms_are_known =
+        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
     ASSERT_EQ(4, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           8) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(2, fact_manager.GetSynonymsForId(8).size());
+    ASSERT_EQ(2, fact_manager.GetSynonymsForId(8, context.get()).size());
     protobufs::DataDescriptor descriptor_101 = MakeDataDescriptor(101, {});
-    ASSERT_TRUE(
-        fact_manager.IsSynonymous(MakeDataDescriptor(8, {}), descriptor_101));
+    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(8, {}),
+                                          descriptor_101, context.get()));
   }
 
   {
@@ -94,16 +95,16 @@ TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
         101, MakeInstructionDescriptor(5, SpvOpReturn, 0), 102);
     ASSERT_TRUE(copy_false_again.IsApplicable(context.get(), fact_manager));
     copy_false_again.Apply(context.get(), &fact_manager);
-    auto ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown();
+    std::vector<uint32_t> ids_for_which_synonyms_are_known =
+        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
     ASSERT_EQ(5, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           101) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(3, fact_manager.GetSynonymsForId(101).size());
+    ASSERT_EQ(3, fact_manager.GetSynonymsForId(101, context.get()).size());
     protobufs::DataDescriptor descriptor_102 = MakeDataDescriptor(102, {});
-    ASSERT_TRUE(
-        fact_manager.IsSynonymous(MakeDataDescriptor(101, {}), descriptor_102));
+    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(101, {}),
+                                          descriptor_102, context.get()));
   }
 
   {
@@ -111,16 +112,16 @@ TEST(TransformationCopyObjectTest, CopyBooleanConstants) {
         7, MakeInstructionDescriptor(102, SpvOpReturn, 0), 103);
     ASSERT_TRUE(copy_true_again.IsApplicable(context.get(), fact_manager));
     copy_true_again.Apply(context.get(), &fact_manager);
-    auto ids_for_which_synonyms_are_known =
-        fact_manager.GetIdsForWhichSynonymsAreKnown();
+    std::vector<uint32_t> ids_for_which_synonyms_are_known =
+        fact_manager.GetIdsForWhichSynonymsAreKnown(context.get());
     ASSERT_EQ(6, ids_for_which_synonyms_are_known.size());
     ASSERT_TRUE(std::find(ids_for_which_synonyms_are_known.begin(),
                           ids_for_which_synonyms_are_known.end(),
                           7) != ids_for_which_synonyms_are_known.end());
-    ASSERT_EQ(3, fact_manager.GetSynonymsForId(7).size());
+    ASSERT_EQ(3, fact_manager.GetSynonymsForId(7, context.get()).size());
     protobufs::DataDescriptor descriptor_103 = MakeDataDescriptor(103, {});
-    ASSERT_TRUE(
-        fact_manager.IsSynonymous(MakeDataDescriptor(7, {}), descriptor_103));
+    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
+                                          descriptor_103, context.get()));
   }
 
   std::string after_transformation = R"(

+ 1213 - 0
3rdparty/spirv-tools/test/fuzz/transformation_replace_id_with_synonym_test.cpp

@@ -0,0 +1,1213 @@
+// 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_id_with_synonym.h"
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+// The following shader was obtained from this GLSL, which was then optimized
+// with spirv-opt -O and manually edited to include some uses of OpCopyObject
+// (to introduce id synonyms).
+//
+// #version 310 es
+//
+// precision highp int;
+// precision highp float;
+//
+// layout(set = 0, binding = 0) uniform buf {
+//   int a;
+//   int b;
+//   int c;
+// };
+//
+// layout(location = 0) out vec4 color;
+//
+// void main() {
+//   int x = a;
+//   float f = 0.0;
+//   while (x < b) {
+//     switch(x % 4) {
+//       case 0:
+//         color[0] = f;
+//         break;
+//       case 1:
+//         color[1] = f;
+//         break;
+//       case 2:
+//         color[2] = f;
+//         break;
+//       case 3:
+//         color[3] = f;
+//         break;
+//       default:
+//         break;
+//     }
+//     if (x > c) {
+//       x++;
+//     } else {
+//       x += 2;
+//     }
+//   }
+//   color[0] += color[1] + float(x);
+// }
+const std::string kComplexShader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %42
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "buf"
+               OpMemberName %9 0 "a"
+               OpMemberName %9 1 "b"
+               OpMemberName %9 2 "c"
+               OpName %11 ""
+               OpName %42 "color"
+               OpMemberDecorate %9 0 Offset 0
+               OpMemberDecorate %9 1 Offset 4
+               OpMemberDecorate %9 2 Offset 8
+               OpDecorate %9 Block
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 0
+               OpDecorate %42 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpTypeStruct %6 %6 %6
+         %10 = OpTypePointer Uniform %9
+         %11 = OpVariable %10 Uniform
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer Uniform %6
+         %16 = OpTypeFloat 32
+         %19 = OpConstant %16 0
+         %26 = OpConstant %6 1
+         %29 = OpTypeBool
+         %32 = OpConstant %6 4
+         %40 = OpTypeVector %16 4
+         %41 = OpTypePointer Output %40
+         %42 = OpVariable %41 Output
+         %44 = OpTypeInt 32 0
+         %45 = OpConstant %44 0
+         %46 = OpTypePointer Output %16
+         %50 = OpConstant %44 1
+         %54 = OpConstant %44 2
+         %58 = OpConstant %44 3
+         %64 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %209 = OpCopyObject %6 %12
+         %14 = OpAccessChain %13 %11 %12
+         %15 = OpLoad %6 %14
+        %200 = OpCopyObject %6 %15
+               OpBranch %20
+         %20 = OpLabel
+         %84 = OpPhi %6 %15 %5 %86 %69
+         %27 = OpAccessChain %13 %11 %26
+         %28 = OpLoad %6 %27
+        %207 = OpCopyObject %6 %84
+        %201 = OpCopyObject %6 %15
+         %30 = OpSLessThan %29 %84 %28
+               OpLoopMerge %22 %69 None
+               OpBranchConditional %30 %21 %22
+         %21 = OpLabel
+         %33 = OpSMod %6 %84 %32
+        %208 = OpCopyObject %6 %33
+               OpSelectionMerge %39 None
+               OpSwitch %33 %38 0 %34 1 %35 2 %36 3 %37
+         %38 = OpLabel
+        %202 = OpCopyObject %6 %15
+               OpBranch %39
+         %34 = OpLabel
+        %210 = OpCopyObject %16 %19
+         %47 = OpAccessChain %46 %42 %45
+               OpStore %47 %19
+               OpBranch %39
+         %35 = OpLabel
+         %51 = OpAccessChain %46 %42 %50
+               OpStore %51 %19
+               OpBranch %39
+         %36 = OpLabel
+        %204 = OpCopyObject %44 %54
+         %55 = OpAccessChain %46 %42 %54
+        %203 = OpCopyObject %46 %55
+               OpStore %55 %19
+               OpBranch %39
+         %37 = OpLabel
+         %59 = OpAccessChain %46 %42 %58
+               OpStore %59 %19
+               OpBranch %39
+         %39 = OpLabel
+        %300 = OpIAdd %6 %15 %15
+         %65 = OpAccessChain %13 %11 %64
+         %66 = OpLoad %6 %65
+         %67 = OpSGreaterThan %29 %84 %66
+               OpSelectionMerge %69 None
+               OpBranchConditional %67 %68 %72
+         %68 = OpLabel
+         %71 = OpIAdd %6 %84 %26
+               OpBranch %69
+         %72 = OpLabel
+         %74 = OpIAdd %6 %84 %64
+        %205 = OpCopyObject %6 %74
+               OpBranch %69
+         %69 = OpLabel
+         %86 = OpPhi %6 %71 %68 %74 %72
+        %301 = OpPhi %6 %71 %68 %15 %72
+               OpBranch %20
+         %22 = OpLabel
+         %75 = OpAccessChain %46 %42 %50
+         %76 = OpLoad %16 %75
+         %78 = OpConvertSToF %16 %84
+         %80 = OpAccessChain %46 %42 %45
+        %206 = OpCopyObject %16 %78
+         %81 = OpLoad %16 %80
+         %79 = OpFAdd %16 %76 %78
+         %82 = OpFAdd %16 %81 %79
+               OpStore %80 %82
+               OpReturn
+               OpFunctionEnd
+)";
+
+protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
+  protobufs::FactDataSynonym data_synonym_fact;
+  *data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
+  *data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
+  protobufs::Fact result;
+  *result.mutable_data_synonym_fact() = data_synonym_fact;
+  return result;
+}
+
+// Equips the fact manager with synonym facts for the above shader.
+void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) {
+  fact_manager->AddFact(MakeSynonymFact(15, 200), context);
+  fact_manager->AddFact(MakeSynonymFact(15, 201), context);
+  fact_manager->AddFact(MakeSynonymFact(15, 202), context);
+  fact_manager->AddFact(MakeSynonymFact(55, 203), context);
+  fact_manager->AddFact(MakeSynonymFact(54, 204), context);
+  fact_manager->AddFact(MakeSynonymFact(74, 205), context);
+  fact_manager->AddFact(MakeSynonymFact(78, 206), context);
+  fact_manager->AddFact(MakeSynonymFact(84, 207), context);
+  fact_manager->AddFact(MakeSynonymFact(33, 208), context);
+  fact_manager->AddFact(MakeSynonymFact(12, 209), context);
+  fact_manager->AddFact(MakeSynonymFact(19, 210), context);
+}
+
+TEST(TransformationReplaceIdWithSynonymTest, IllegalTransformations) {
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, kComplexShader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  SetUpIdSynonyms(&fact_manager, context.get());
+
+  // %202 cannot replace %15 as in-operand 0 of %300, since %202 does not
+  // dominate %300.
+  auto synonym_does_not_dominate_use = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(15, MakeInstructionDescriptor(300, SpvOpIAdd, 0), 0),
+      202);
+  ASSERT_FALSE(
+      synonym_does_not_dominate_use.IsApplicable(context.get(), fact_manager));
+
+  // %202 cannot replace %15 as in-operand 2 of %301, since this is the OpPhi's
+  // incoming value for block %72, and %202 does not dominate %72.
+  auto synonym_does_not_dominate_use_op_phi =
+      TransformationReplaceIdWithSynonym(
+          MakeIdUseDescriptor(15, MakeInstructionDescriptor(301, SpvOpPhi, 0),
+                              2),
+          202);
+  ASSERT_FALSE(synonym_does_not_dominate_use_op_phi.IsApplicable(context.get(),
+                                                                 fact_manager));
+
+  // %200 is not a synonym for %84
+  auto id_in_use_is_not_synonymous = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          84, MakeInstructionDescriptor(67, SpvOpSGreaterThan, 0), 0),
+      200);
+  ASSERT_FALSE(
+      id_in_use_is_not_synonymous.IsApplicable(context.get(), fact_manager));
+
+  // %86 is not a synonym for anything (and in particular not for %74)
+  auto id_has_no_synonyms = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(86, MakeInstructionDescriptor(84, SpvOpPhi, 0), 2),
+      74);
+  ASSERT_FALSE(id_has_no_synonyms.IsApplicable(context.get(), fact_manager));
+
+  // This would lead to %207 = 'OpCopyObject %type %207' if it were allowed
+  auto synonym_use_is_in_synonym_definition =
+      TransformationReplaceIdWithSynonym(
+          MakeIdUseDescriptor(
+              84, MakeInstructionDescriptor(207, SpvOpCopyObject, 0), 0),
+          207);
+  ASSERT_FALSE(synonym_use_is_in_synonym_definition.IsApplicable(context.get(),
+                                                                 fact_manager));
+
+  // The id use descriptor does not lead to a use (%84 is not used in the
+  // definition of %207)
+  auto bad_id_use_descriptor = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          84, MakeInstructionDescriptor(200, SpvOpCopyObject, 0), 0),
+      207);
+  ASSERT_FALSE(bad_id_use_descriptor.IsApplicable(context.get(), fact_manager));
+
+  // This replacement would lead to an access chain into a struct using a
+  // non-constant index.
+  auto bad_access_chain = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          12, MakeInstructionDescriptor(14, SpvOpAccessChain, 0), 1),
+      209);
+  ASSERT_FALSE(bad_access_chain.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceIdWithSynonymTest, LegalTransformations) {
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, kComplexShader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  SetUpIdSynonyms(&fact_manager, context.get());
+
+  auto global_constant_synonym = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(19, MakeInstructionDescriptor(47, SpvOpStore, 0), 1),
+      210);
+  ASSERT_TRUE(
+      global_constant_synonym.IsApplicable(context.get(), fact_manager));
+  global_constant_synonym.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto replace_vector_access_chain_index = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          54, MakeInstructionDescriptor(55, SpvOpAccessChain, 0), 1),
+      204);
+  ASSERT_TRUE(replace_vector_access_chain_index.IsApplicable(context.get(),
+                                                             fact_manager));
+  replace_vector_access_chain_index.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // This is an interesting case because it replaces something that is being
+  // copied with something that is already a synonym.
+  auto regular_replacement = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          15, MakeInstructionDescriptor(202, SpvOpCopyObject, 0), 0),
+      201);
+  ASSERT_TRUE(regular_replacement.IsApplicable(context.get(), fact_manager));
+  regular_replacement.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto regular_replacement2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(55, MakeInstructionDescriptor(203, SpvOpStore, 0), 0),
+      203);
+  ASSERT_TRUE(regular_replacement2.IsApplicable(context.get(), fact_manager));
+  regular_replacement2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto good_op_phi = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(74, MakeInstructionDescriptor(86, SpvOpPhi, 0), 2),
+      205);
+  ASSERT_TRUE(good_op_phi.IsApplicable(context.get(), fact_manager));
+  good_op_phi.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %42
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %9 "buf"
+               OpMemberName %9 0 "a"
+               OpMemberName %9 1 "b"
+               OpMemberName %9 2 "c"
+               OpName %11 ""
+               OpName %42 "color"
+               OpMemberDecorate %9 0 Offset 0
+               OpMemberDecorate %9 1 Offset 4
+               OpMemberDecorate %9 2 Offset 8
+               OpDecorate %9 Block
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 0
+               OpDecorate %42 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpTypeStruct %6 %6 %6
+         %10 = OpTypePointer Uniform %9
+         %11 = OpVariable %10 Uniform
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer Uniform %6
+         %16 = OpTypeFloat 32
+         %19 = OpConstant %16 0
+         %26 = OpConstant %6 1
+         %29 = OpTypeBool
+         %32 = OpConstant %6 4
+         %40 = OpTypeVector %16 4
+         %41 = OpTypePointer Output %40
+         %42 = OpVariable %41 Output
+         %44 = OpTypeInt 32 0
+         %45 = OpConstant %44 0
+         %46 = OpTypePointer Output %16
+         %50 = OpConstant %44 1
+         %54 = OpConstant %44 2
+         %58 = OpConstant %44 3
+         %64 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %209 = OpCopyObject %6 %12
+         %14 = OpAccessChain %13 %11 %12
+         %15 = OpLoad %6 %14
+        %200 = OpCopyObject %6 %15
+               OpBranch %20
+         %20 = OpLabel
+         %84 = OpPhi %6 %15 %5 %86 %69
+         %27 = OpAccessChain %13 %11 %26
+         %28 = OpLoad %6 %27
+        %207 = OpCopyObject %6 %84
+        %201 = OpCopyObject %6 %15
+         %30 = OpSLessThan %29 %84 %28
+               OpLoopMerge %22 %69 None
+               OpBranchConditional %30 %21 %22
+         %21 = OpLabel
+         %33 = OpSMod %6 %84 %32
+        %208 = OpCopyObject %6 %33
+               OpSelectionMerge %39 None
+               OpSwitch %33 %38 0 %34 1 %35 2 %36 3 %37
+         %38 = OpLabel
+        %202 = OpCopyObject %6 %201
+               OpBranch %39
+         %34 = OpLabel
+        %210 = OpCopyObject %16 %19
+         %47 = OpAccessChain %46 %42 %45
+               OpStore %47 %210
+               OpBranch %39
+         %35 = OpLabel
+         %51 = OpAccessChain %46 %42 %50
+               OpStore %51 %19
+               OpBranch %39
+         %36 = OpLabel
+        %204 = OpCopyObject %44 %54
+         %55 = OpAccessChain %46 %42 %204
+        %203 = OpCopyObject %46 %55
+               OpStore %203 %19
+               OpBranch %39
+         %37 = OpLabel
+         %59 = OpAccessChain %46 %42 %58
+               OpStore %59 %19
+               OpBranch %39
+         %39 = OpLabel
+        %300 = OpIAdd %6 %15 %15
+         %65 = OpAccessChain %13 %11 %64
+         %66 = OpLoad %6 %65
+         %67 = OpSGreaterThan %29 %84 %66
+               OpSelectionMerge %69 None
+               OpBranchConditional %67 %68 %72
+         %68 = OpLabel
+         %71 = OpIAdd %6 %84 %26
+               OpBranch %69
+         %72 = OpLabel
+         %74 = OpIAdd %6 %84 %64
+        %205 = OpCopyObject %6 %74
+               OpBranch %69
+         %69 = OpLabel
+         %86 = OpPhi %6 %71 %68 %205 %72
+        %301 = OpPhi %6 %71 %68 %15 %72
+               OpBranch %20
+         %22 = OpLabel
+         %75 = OpAccessChain %46 %42 %50
+         %76 = OpLoad %16 %75
+         %78 = OpConvertSToF %16 %84
+         %80 = OpAccessChain %46 %42 %45
+        %206 = OpCopyObject %16 %78
+         %81 = OpLoad %16 %80
+         %79 = OpFAdd %16 %76 %78
+         %82 = OpFAdd %16 %81 %79
+               OpStore %80 %82
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfVariables) {
+  // The following SPIR-V comes from this GLSL, with object copies added:
+  //
+  // #version 310 es
+  //
+  // precision highp int;
+  //
+  // int g;
+  //
+  // void main() {
+  //   int l;
+  //   l = g;
+  //   g = l;
+  // }
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "l"
+               OpName %10 "g"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypePointer Private %6
+         %10 = OpVariable %9 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpCopyObject %9 %10
+        %101 = OpCopyObject %7 %8
+         %11 = OpLoad %6 %10
+               OpStore %8 %11
+         %12 = OpLoad %6 %8
+               OpStore %10 %12
+               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;
+
+  fact_manager.AddFact(MakeSynonymFact(10, 100), context.get());
+  fact_manager.AddFact(MakeSynonymFact(8, 101), context.get());
+
+  // Replace %10 with %100 in:
+  // %11 = OpLoad %6 %10
+  auto replacement1 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(10, MakeInstructionDescriptor(11, SpvOpLoad, 0), 0),
+      100);
+  ASSERT_TRUE(replacement1.IsApplicable(context.get(), fact_manager));
+  replacement1.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %8 with %101 in:
+  // OpStore %8 %11
+  auto replacement2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(8, MakeInstructionDescriptor(11, SpvOpStore, 0), 0),
+      101);
+  ASSERT_TRUE(replacement2.IsApplicable(context.get(), fact_manager));
+  replacement2.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %8 with %101 in:
+  // %12 = OpLoad %6 %8
+  auto replacement3 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(8, MakeInstructionDescriptor(12, SpvOpLoad, 0), 0),
+      101);
+  ASSERT_TRUE(replacement3.IsApplicable(context.get(), fact_manager));
+  replacement3.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replace %10 with %100 in:
+  // OpStore %10 %12
+  auto replacement4 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(10, MakeInstructionDescriptor(12, SpvOpStore, 0), 0),
+      100);
+  ASSERT_TRUE(replacement4.IsApplicable(context.get(), fact_manager));
+  replacement4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "l"
+               OpName %10 "g"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypePointer Private %6
+         %10 = OpVariable %9 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpCopyObject %9 %10
+        %101 = OpCopyObject %7 %8
+         %11 = OpLoad %6 %100
+               OpStore %101 %11
+         %12 = OpLoad %6 %101
+               OpStore %100 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceIdWithSynonymTest,
+     SynonymOfVariableNoGoodInFunctionCall) {
+  // The following SPIR-V comes from this GLSL, with an object copy added:
+  //
+  // #version 310 es
+  //
+  // precision highp int;
+  //
+  // void foo(int x) { }
+  //
+  // void main() {
+  //   int a;
+  //   a = 2;
+  //   foo(a);
+  // }
+  const 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 %10 "foo(i1;"
+               OpName %9 "x"
+               OpName %12 "a"
+               OpName %14 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %12 %13
+         %15 = OpLoad %6 %12
+               OpStore %14 %15
+        %100 = OpCopyObject %7 %14
+         %16 = OpFunctionCall %2 %10 %14
+               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;
+
+  fact_manager.AddFact(MakeSynonymFact(14, 100), context.get());
+
+  // Replace %14 with %100 in:
+  // %16 = OpFunctionCall %2 %10 %14
+  auto replacement = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          14, MakeInstructionDescriptor(16, SpvOpFunctionCall, 0), 1),
+      100);
+  ASSERT_FALSE(replacement.IsApplicable(context.get(), fact_manager));
+}
+
+TEST(TransformationReplaceIdWithSynonymTest, SynonymsOfAccessChainIndices) {
+  // The following SPIR-V comes from this GLSL, with object copies added:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  // precision highp int;
+  //
+  // struct S {
+  //   int[3] a;
+  //   vec4 b;
+  //   bool c;
+  // } d;
+  //
+  // float[20] e;
+  //
+  // struct T {
+  //   float f;
+  //   S g;
+  // } h;
+  //
+  // T[4] i;
+  //
+  // void main() {
+  //   d.a[2] = 10;
+  //   d.b[3] = 11.0;
+  //   d.c = false;
+  //   e[17] = 12.0;
+  //   h.f = 13.0;
+  //   h.g.a[1] = 14;
+  //   h.g.b[0] = 15.0;
+  //   h.g.c = true;
+  //   i[0].f = 16.0;
+  //   i[1].g.a[0] = 17;
+  //   i[2].g.b[1] = 18.0;
+  //   i[3].g.c = true;
+  // }
+  const 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 %13 "S"
+               OpMemberName %13 0 "a"
+               OpMemberName %13 1 "b"
+               OpMemberName %13 2 "c"
+               OpName %15 "d"
+               OpName %31 "e"
+               OpName %35 "T"
+               OpMemberName %35 0 "f"
+               OpMemberName %35 1 "g"
+               OpName %37 "h"
+               OpName %50 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 3
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeFloat 32
+         %11 = OpTypeVector %10 4
+         %12 = OpTypeBool
+         %13 = OpTypeStruct %9 %11 %12
+         %14 = OpTypePointer Private %13
+         %15 = OpVariable %14 Private
+         %16 = OpConstant %6 0
+         %17 = OpConstant %6 2
+         %18 = OpConstant %6 10
+         %19 = OpTypePointer Private %6
+         %21 = OpConstant %6 1
+         %22 = OpConstant %10 11
+         %23 = OpTypePointer Private %10
+         %25 = OpConstantFalse %12
+         %26 = OpTypePointer Private %12
+         %28 = OpConstant %7 20
+         %29 = OpTypeArray %10 %28
+         %30 = OpTypePointer Private %29
+         %31 = OpVariable %30 Private
+         %32 = OpConstant %6 17
+         %33 = OpConstant %10 12
+         %35 = OpTypeStruct %10 %13
+         %36 = OpTypePointer Private %35
+         %37 = OpVariable %36 Private
+         %38 = OpConstant %10 13
+         %40 = OpConstant %6 14
+         %42 = OpConstant %10 15
+         %43 = OpConstant %7 0
+         %45 = OpConstantTrue %12
+         %47 = OpConstant %7 4
+         %48 = OpTypeArray %35 %47
+         %49 = OpTypePointer Private %48
+         %50 = OpVariable %49 Private
+         %51 = OpConstant %10 16
+         %54 = OpConstant %10 18
+         %55 = OpConstant %7 1
+         %57 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+
+         %100 = OpCopyObject %6 %16 ; 0
+         %101 = OpCopyObject %6 %21 ; 1
+         %102 = OpCopyObject %6 %17 ; 2
+         %103 = OpCopyObject %6 %57 ; 3
+         %104 = OpCopyObject %6 %18 ; 10
+         %105 = OpCopyObject %6 %40 ; 14
+         %106 = OpCopyObject %6 %32 ; 17
+         %107 = OpCopyObject %7 %43 ; 0
+         %108 = OpCopyObject %7 %55 ; 1
+         %109 = OpCopyObject %7  %8 ; 3
+         %110 = OpCopyObject %7 %47 ; 4
+         %111 = OpCopyObject %7 %28 ; 20
+         %112 = OpCopyObject %12 %45 ; true
+
+         %20 = OpAccessChain %19 %15 %16 %17
+               OpStore %20 %18
+         %24 = OpAccessChain %23 %15 %21 %8
+               OpStore %24 %22
+         %27 = OpAccessChain %26 %15 %17
+               OpStore %27 %25
+         %34 = OpAccessChain %23 %31 %32
+               OpStore %34 %33
+         %39 = OpAccessChain %23 %37 %16
+               OpStore %39 %38
+         %41 = OpAccessChain %19 %37 %21 %16 %21
+               OpStore %41 %40
+         %44 = OpAccessChain %23 %37 %21 %21 %43
+               OpStore %44 %42
+         %46 = OpAccessChain %26 %37 %21 %17
+               OpStore %46 %45
+         %52 = OpAccessChain %23 %50 %16 %16
+               OpStore %52 %51
+         %53 = OpAccessChain %19 %50 %21 %21 %16 %16
+               OpStore %53 %32
+         %56 = OpAccessChain %23 %50 %17 %21 %21 %55
+               OpStore %56 %54
+         %58 = OpAccessChain %26 %50 %57 %21 %17
+               OpStore %58 %45
+               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;
+
+  // Add synonym facts corresponding to the OpCopyObject operations that have
+  // been applied to all constants in the module.
+  fact_manager.AddFact(MakeSynonymFact(16, 100), context.get());
+  fact_manager.AddFact(MakeSynonymFact(21, 101), context.get());
+  fact_manager.AddFact(MakeSynonymFact(17, 102), context.get());
+  fact_manager.AddFact(MakeSynonymFact(57, 103), context.get());
+  fact_manager.AddFact(MakeSynonymFact(18, 104), context.get());
+  fact_manager.AddFact(MakeSynonymFact(40, 105), context.get());
+  fact_manager.AddFact(MakeSynonymFact(32, 106), context.get());
+  fact_manager.AddFact(MakeSynonymFact(43, 107), context.get());
+  fact_manager.AddFact(MakeSynonymFact(55, 108), context.get());
+  fact_manager.AddFact(MakeSynonymFact(8, 109), context.get());
+  fact_manager.AddFact(MakeSynonymFact(47, 110), context.get());
+  fact_manager.AddFact(MakeSynonymFact(28, 111), context.get());
+  fact_manager.AddFact(MakeSynonymFact(45, 112), context.get());
+
+  // Replacements of the form %16 -> %100
+
+  // %20 = OpAccessChain %19 %15 *%16* %17
+  // Corresponds to d.*a*[2]
+  // The index %16 used for a cannot be replaced
+  auto replacement1 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(20, SpvOpAccessChain, 0), 1),
+      100);
+  ASSERT_FALSE(replacement1.IsApplicable(context.get(), fact_manager));
+
+  // %39 = OpAccessChain %23 %37 *%16*
+  // Corresponds to h.*f*
+  // The index %16 used for f cannot be replaced
+  auto replacement2 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(39, SpvOpAccessChain, 0), 1),
+      100);
+  ASSERT_FALSE(replacement2.IsApplicable(context.get(), fact_manager));
+
+  // %41 = OpAccessChain %19 %37 %21 *%16* %21
+  // Corresponds to h.g.*a*[1]
+  // The index %16 used for a cannot be replaced
+  auto replacement3 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(41, SpvOpAccessChain, 0), 2),
+      100);
+  ASSERT_FALSE(replacement3.IsApplicable(context.get(), fact_manager));
+
+  // %52 = OpAccessChain %23 %50 *%16* %16
+  // Corresponds to i[*0*].f
+  // The index %16 used for 0 *can* be replaced
+  auto replacement4 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(52, SpvOpAccessChain, 0), 1),
+      100);
+  ASSERT_TRUE(replacement4.IsApplicable(context.get(), fact_manager));
+  replacement4.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %52 = OpAccessChain %23 %50 %16 *%16*
+  // Corresponds to i[0].*f*
+  // The index %16 used for f cannot be replaced
+  auto replacement5 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(52, SpvOpAccessChain, 0), 2),
+      100);
+  ASSERT_FALSE(replacement5.IsApplicable(context.get(), fact_manager));
+
+  // %53 = OpAccessChain %19 %50 %21 %21 *%16* %16
+  // Corresponds to i[1].g.*a*[0]
+  // The index %16 used for a cannot be replaced
+  auto replacement6 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 3),
+      100);
+  ASSERT_FALSE(replacement6.IsApplicable(context.get(), fact_manager));
+
+  // %53 = OpAccessChain %19 %50 %21 %21 %16 *%16*
+  // Corresponds to i[1].g.a[*0*]
+  // The index %16 used for 0 *can* be replaced
+  auto replacement7 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          16, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 4),
+      100);
+  ASSERT_TRUE(replacement7.IsApplicable(context.get(), fact_manager));
+  replacement7.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replacements of the form %21 -> %101
+
+  // %24 = OpAccessChain %23 %15 *%21* %8
+  // Corresponds to d.*b*[3]
+  // The index %24 used for b cannot be replaced
+  auto replacement8 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(24, SpvOpAccessChain, 0), 1),
+      101);
+  ASSERT_FALSE(replacement8.IsApplicable(context.get(), fact_manager));
+
+  // %41 = OpAccessChain %19 %37 *%21* %16 %21
+  // Corresponds to h.*g*.a[1]
+  // The index %24 used for g cannot be replaced
+  auto replacement9 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(41, SpvOpAccessChain, 0), 1),
+      101);
+  ASSERT_FALSE(replacement9.IsApplicable(context.get(), fact_manager));
+
+  // %41 = OpAccessChain %19 %37 %21 %16 *%21*
+  // Corresponds to h.g.a[*1*]
+  // The index %24 used for 1 *can* be replaced
+  auto replacement10 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(41, SpvOpAccessChain, 0), 3),
+      101);
+  ASSERT_TRUE(replacement10.IsApplicable(context.get(), fact_manager));
+  replacement10.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %44 = OpAccessChain %23 %37 *%21* %21 %43
+  // Corresponds to h.*g*.b[0]
+  // The index %24 used for g cannot be replaced
+  auto replacement11 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(44, SpvOpAccessChain, 0), 1),
+      101);
+  ASSERT_FALSE(replacement11.IsApplicable(context.get(), fact_manager));
+
+  // %44 = OpAccessChain %23 %37 %21 *%21* %43
+  // Corresponds to h.g.*b*[0]
+  // The index %24 used for b cannot be replaced
+  auto replacement12 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(44, SpvOpAccessChain, 0), 2),
+      101);
+  ASSERT_FALSE(replacement12.IsApplicable(context.get(), fact_manager));
+
+  // %46 = OpAccessChain %26 %37 *%21* %17
+  // Corresponds to h.*g*.c
+  // The index %24 used for g cannot be replaced
+  auto replacement13 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(46, SpvOpAccessChain, 0), 1),
+      101);
+  ASSERT_FALSE(replacement13.IsApplicable(context.get(), fact_manager));
+
+  // %53 = OpAccessChain %19 %50 *%21* %21 %16 %16
+  // Corresponds to i[*1*].g.a[0]
+  // The index %24 used for 1 *can* be replaced
+  auto replacement14 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 1),
+      101);
+  ASSERT_TRUE(replacement14.IsApplicable(context.get(), fact_manager));
+  replacement14.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %53 = OpAccessChain %19 %50 %21 *%21* %16 %16
+  // Corresponds to i[1].*g*.a[0]
+  // The index %24 used for g cannot be replaced
+  auto replacement15 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 2),
+      101);
+  ASSERT_FALSE(replacement15.IsApplicable(context.get(), fact_manager));
+
+  // %56 = OpAccessChain %23 %50 %17 *%21* %21 %55
+  // Corresponds to i[2].*g*.b[1]
+  // The index %24 used for g cannot be replaced
+  auto replacement16 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 2),
+      101);
+  ASSERT_FALSE(replacement16.IsApplicable(context.get(), fact_manager));
+
+  // %56 = OpAccessChain %23 %50 %17 %21 *%21* %55
+  // Corresponds to i[2].g.*b*[1]
+  // The index %24 used for b cannot be replaced
+  auto replacement17 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 3),
+      101);
+  ASSERT_FALSE(replacement17.IsApplicable(context.get(), fact_manager));
+
+  // %58 = OpAccessChain %26 %50 %57 *%21* %17
+  // Corresponds to i[3].*g*.c
+  // The index %24 used for g cannot be replaced
+  auto replacement18 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          21, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 2),
+      101);
+  ASSERT_FALSE(replacement18.IsApplicable(context.get(), fact_manager));
+
+  // Replacements of the form %17 -> %102
+
+  // %20 = OpAccessChain %19 %15 %16 %17
+  // Corresponds to d.a[*2*]
+  // The index %17 used for 2 *can* be replaced
+  auto replacement19 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          17, MakeInstructionDescriptor(20, SpvOpAccessChain, 0), 2),
+      102);
+  ASSERT_TRUE(replacement19.IsApplicable(context.get(), fact_manager));
+  replacement19.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %27 = OpAccessChain %26 %15 %17
+  // Corresponds to d.c
+  // The index %17 used for c cannot be replaced
+  auto replacement20 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          17, MakeInstructionDescriptor(27, SpvOpAccessChain, 0), 1),
+      102);
+  ASSERT_FALSE(replacement20.IsApplicable(context.get(), fact_manager));
+
+  // %46 = OpAccessChain %26 %37 %21 %17
+  // Corresponds to h.g.*c*
+  // The index %17 used for c cannot be replaced
+  auto replacement21 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          17, MakeInstructionDescriptor(46, SpvOpAccessChain, 0), 2),
+      102);
+  ASSERT_FALSE(replacement21.IsApplicable(context.get(), fact_manager));
+
+  // %56 = OpAccessChain %23 %50 %17 %21 %21 %55
+  // Corresponds to i[*2*].g.b[1]
+  // The index %17 used for 2 *can* be replaced
+  auto replacement22 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          17, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 1),
+      102);
+  ASSERT_TRUE(replacement22.IsApplicable(context.get(), fact_manager));
+  replacement22.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %58 = OpAccessChain %26 %50 %57 %21 %17
+  // Corresponds to i[3].g.*c*
+  // The index %17 used for c cannot be replaced
+  auto replacement23 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          17, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 3),
+      102);
+  ASSERT_FALSE(replacement23.IsApplicable(context.get(), fact_manager));
+
+  // Replacements of the form %57 -> %103
+
+  // %58 = OpAccessChain %26 %50 *%57* %21 %17
+  // Corresponds to i[*3*].g.c
+  // The index %57 used for 3 *can* be replaced
+  auto replacement24 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          57, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 1),
+      103);
+  ASSERT_TRUE(replacement24.IsApplicable(context.get(), fact_manager));
+  replacement24.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replacements of the form %32 -> %106
+
+  // %34 = OpAccessChain %23 %31 *%32*
+  // Corresponds to e[*17*]
+  // The index %32 used for 17 *can* be replaced
+  auto replacement25 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          32, MakeInstructionDescriptor(34, SpvOpAccessChain, 0), 1),
+      106);
+  ASSERT_TRUE(replacement25.IsApplicable(context.get(), fact_manager));
+  replacement25.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replacements of the form %43 -> %107
+
+  // %44 = OpAccessChain %23 %37 %21 %21 *%43*
+  // Corresponds to h.g.b[*0*]
+  // The index %43 used for 0 *can* be replaced
+  auto replacement26 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          43, MakeInstructionDescriptor(44, SpvOpAccessChain, 0), 3),
+      107);
+  ASSERT_TRUE(replacement26.IsApplicable(context.get(), fact_manager));
+  replacement26.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replacements of the form %55 -> %108
+
+  // %56 = OpAccessChain %23 %50 %17 %21 %21 *%55*
+  // Corresponds to i[2].g.b[*1*]
+  // The index %55 used for 1 *can* be replaced
+  auto replacement27 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(
+          55, MakeInstructionDescriptor(56, SpvOpAccessChain, 0), 4),
+      108);
+  ASSERT_TRUE(replacement27.IsApplicable(context.get(), fact_manager));
+  replacement27.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Replacements of the form %8 -> %109
+
+  // %24 = OpAccessChain %23 %15 %21 *%8*
+  // Corresponds to d.b[*3*]
+  // The index %8 used for 3 *can* be replaced
+  auto replacement28 = TransformationReplaceIdWithSynonym(
+      MakeIdUseDescriptor(8, MakeInstructionDescriptor(24, SpvOpAccessChain, 0),
+                          2),
+      109);
+  ASSERT_TRUE(replacement28.IsApplicable(context.get(), fact_manager));
+  replacement28.Apply(context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %13 "S"
+               OpMemberName %13 0 "a"
+               OpMemberName %13 1 "b"
+               OpMemberName %13 2 "c"
+               OpName %15 "d"
+               OpName %31 "e"
+               OpName %35 "T"
+               OpMemberName %35 0 "f"
+               OpMemberName %35 1 "g"
+               OpName %37 "h"
+               OpName %50 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 3
+          %9 = OpTypeArray %6 %8
+         %10 = OpTypeFloat 32
+         %11 = OpTypeVector %10 4
+         %12 = OpTypeBool
+         %13 = OpTypeStruct %9 %11 %12
+         %14 = OpTypePointer Private %13
+         %15 = OpVariable %14 Private
+         %16 = OpConstant %6 0
+         %17 = OpConstant %6 2
+         %18 = OpConstant %6 10
+         %19 = OpTypePointer Private %6
+         %21 = OpConstant %6 1
+         %22 = OpConstant %10 11
+         %23 = OpTypePointer Private %10
+         %25 = OpConstantFalse %12
+         %26 = OpTypePointer Private %12
+         %28 = OpConstant %7 20
+         %29 = OpTypeArray %10 %28
+         %30 = OpTypePointer Private %29
+         %31 = OpVariable %30 Private
+         %32 = OpConstant %6 17
+         %33 = OpConstant %10 12
+         %35 = OpTypeStruct %10 %13
+         %36 = OpTypePointer Private %35
+         %37 = OpVariable %36 Private
+         %38 = OpConstant %10 13
+         %40 = OpConstant %6 14
+         %42 = OpConstant %10 15
+         %43 = OpConstant %7 0
+         %45 = OpConstantTrue %12
+         %47 = OpConstant %7 4
+         %48 = OpTypeArray %35 %47
+         %49 = OpTypePointer Private %48
+         %50 = OpVariable %49 Private
+         %51 = OpConstant %10 16
+         %54 = OpConstant %10 18
+         %55 = OpConstant %7 1
+         %57 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+
+         %100 = OpCopyObject %6 %16 ; 0
+         %101 = OpCopyObject %6 %21 ; 1
+         %102 = OpCopyObject %6 %17 ; 2
+         %103 = OpCopyObject %6 %57 ; 3
+         %104 = OpCopyObject %6 %18 ; 10
+         %105 = OpCopyObject %6 %40 ; 14
+         %106 = OpCopyObject %6 %32 ; 17
+         %107 = OpCopyObject %7 %43 ; 0
+         %108 = OpCopyObject %7 %55 ; 1
+         %109 = OpCopyObject %7  %8 ; 3
+         %110 = OpCopyObject %7 %47 ; 4
+         %111 = OpCopyObject %7 %28 ; 20
+         %112 = OpCopyObject %12 %45 ; true
+
+         %20 = OpAccessChain %19 %15 %16 %102
+               OpStore %20 %18
+         %24 = OpAccessChain %23 %15 %21 %109
+               OpStore %24 %22
+         %27 = OpAccessChain %26 %15 %17
+               OpStore %27 %25
+         %34 = OpAccessChain %23 %31 %106
+               OpStore %34 %33
+         %39 = OpAccessChain %23 %37 %16
+               OpStore %39 %38
+         %41 = OpAccessChain %19 %37 %21 %16 %101
+               OpStore %41 %40
+         %44 = OpAccessChain %23 %37 %21 %21 %107
+               OpStore %44 %42
+         %46 = OpAccessChain %26 %37 %21 %17
+               OpStore %46 %45
+         %52 = OpAccessChain %23 %50 %100 %16
+               OpStore %52 %51
+         %53 = OpAccessChain %19 %50 %101 %21 %16 %100
+               OpStore %53 %32
+         %56 = OpAccessChain %23 %50 %102 %21 %21 %108
+               OpStore %56 %54
+         %58 = OpAccessChain %26 %50 %103 %21 %17
+               OpStore %58 %45
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools

+ 532 - 0
3rdparty/spirv-tools/test/fuzz/transformation_vector_shuffle_test.cpp

@@ -0,0 +1,532 @@
+// 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_vector_shuffle.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationVectorShuffle, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypeVector %6 2
+         %10 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %12 = OpConstantComposite %7 %10 %11
+        %112 = OpUndef %7
+         %13 = OpTypeVector %6 3
+         %16 = OpConstantComposite %13 %10 %11 %10
+         %17 = OpTypeVector %6 4
+         %20 = OpConstantComposite %17 %10 %11 %10 %11
+         %21 = OpTypeInt 32 1
+         %22 = OpTypeVector %21 2
+         %25 = OpConstant %21 1
+         %26 = OpConstant %21 0
+         %27 = OpConstantComposite %22 %25 %26
+         %28 = OpTypeVector %21 3
+         %31 = OpConstantComposite %28 %25 %26 %25
+         %32 = OpTypeVector %21 4
+         %33 = OpTypePointer Function %32
+         %35 = OpConstantComposite %32 %25 %26 %25 %26
+         %36 = OpTypeInt 32 0
+         %37 = OpTypeVector %36 2
+         %40 = OpConstant %36 1
+         %41 = OpConstant %36 0
+         %42 = OpConstantComposite %37 %40 %41
+         %43 = OpTypeVector %36 3
+         %46 = OpConstantComposite %43 %40 %41 %40
+         %47 = OpTypeVector %36 4
+         %50 = OpConstantComposite %47 %40 %41 %40 %41
+         %51 = OpTypeFloat 32
+         %55 = OpConstant %51 1
+         %56 = OpConstant %51 0
+         %58 = OpTypeVector %51 3
+         %61 = OpConstantComposite %58 %55 %56 %55
+         %62 = OpTypeVector %51 4
+         %65 = OpConstantComposite %62 %55 %56 %55 %56
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %100 None
+               OpBranchConditional %10 %101 %102
+        %101 = OpLabel
+        %103 = OpCompositeConstruct %62 %55 %55 %55 %56
+               OpBranch %100
+        %102 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(12, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
+                                  MakeDataDescriptor(12, {1}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(16, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
+                                  MakeDataDescriptor(16, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(16, {2}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(20, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
+                                  MakeDataDescriptor(20, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(20, {2}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(11, {}),
+                                  MakeDataDescriptor(20, {3}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
+                                  MakeDataDescriptor(27, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
+                                  MakeDataDescriptor(27, {1}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
+                                  MakeDataDescriptor(31, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
+                                  MakeDataDescriptor(31, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
+                                  MakeDataDescriptor(31, {2}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
+                                  MakeDataDescriptor(35, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
+                                  MakeDataDescriptor(35, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(25, {}),
+                                  MakeDataDescriptor(35, {2}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(26, {}),
+                                  MakeDataDescriptor(35, {3}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
+                                  MakeDataDescriptor(42, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
+                                  MakeDataDescriptor(42, {1}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
+                                  MakeDataDescriptor(46, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
+                                  MakeDataDescriptor(46, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
+                                  MakeDataDescriptor(46, {2}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
+                                  MakeDataDescriptor(50, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
+                                  MakeDataDescriptor(50, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {}),
+                                  MakeDataDescriptor(50, {2}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(41, {}),
+                                  MakeDataDescriptor(50, {3}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
+                                  MakeDataDescriptor(61, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(56, {}),
+                                  MakeDataDescriptor(61, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
+                                  MakeDataDescriptor(61, {2}), context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
+                                  MakeDataDescriptor(65, {0}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(56, {}),
+                                  MakeDataDescriptor(65, {1}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(55, {}),
+                                  MakeDataDescriptor(65, {2}), context.get());
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(56, {}),
+                                  MakeDataDescriptor(65, {3}), context.get());
+
+  // %103 does not dominate the return instruction.
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 103, 65,
+                   {3, 5, 7})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Illegal to shuffle a bvec2 and a vec3
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 112, 61,
+                   {0, 2, 4})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Illegal to shuffle an ivec2 and a uvec4
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 27, 50,
+                   {1, 3, 5})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Vector 1 does not exist
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 300, 50,
+                   {1, 3, 5})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Vector 2 does not exist
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 27, 300,
+                   {1, 3, 5})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Index out of range
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {0, 20})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Too many indices
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112,
+                   {0, 1, 0, 1, 0, 1, 0, 1})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Too few indices
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Too few indices again
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {0})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Indices define unknown type: we do not have vec2
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 65, 65, {0, 1})
+          .IsApplicable(context.get(), fact_manager));
+
+  // The instruction to insert before does not exist
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(100, SpvOpCompositeConstruct, 1),
+                   201, 20, 12, {0xFFFFFFFF, 3, 5})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // The 'fresh' id is already in use
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(100, SpvOpReturn, 0), 12, 12, 112, {})
+          .IsApplicable(context.get(), fact_manager));
+
+  protobufs::DataDescriptor temp_dd;
+
+  TransformationVectorShuffle transformation1(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {1, 0});
+  ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager));
+  transformation1.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(200, {0});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(200, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}), temp_dd,
+                                        context.get()));
+
+  TransformationVectorShuffle transformation2(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 201, 20, 12,
+      {0xFFFFFFFF, 3, 5});
+  ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager));
+  transformation2.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(201, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(201, {2});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(11, {}), temp_dd,
+                                        context.get()));
+
+  TransformationVectorShuffle transformation3(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 202, 27, 35, {5, 4, 1});
+  ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager));
+  transformation3.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(202, {0});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(202, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(202, {2});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}), temp_dd,
+                                        context.get()));
+
+  TransformationVectorShuffle transformation4(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 203, 42, 46, {0, 1});
+  ASSERT_TRUE(transformation4.IsApplicable(context.get(), fact_manager));
+  transformation4.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(203, {0});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(203, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
+                                        context.get()));
+
+  TransformationVectorShuffle transformation5(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 204, 42, 46, {2, 3, 4});
+  ASSERT_TRUE(transformation5.IsApplicable(context.get(), fact_manager));
+  transformation5.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(204, {0});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(204, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(204, {2});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
+                                        context.get()));
+
+  TransformationVectorShuffle transformation6(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 205, 42, 42,
+      {0, 1, 2, 3});
+  ASSERT_TRUE(transformation6.IsApplicable(context.get(), fact_manager));
+  transformation6.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(205, {0});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(205, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(205, {2});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}), temp_dd,
+                                        context.get()));
+  temp_dd = MakeDataDescriptor(205, {3});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(41, {}), temp_dd,
+                                        context.get()));
+
+  // swizzle vec4 from vec4 and vec4 using some undefs
+  TransformationVectorShuffle transformation7(
+      MakeInstructionDescriptor(100, SpvOpReturn, 0), 206, 65, 65,
+      {0xFFFFFFFF, 3, 6, 0xFFFFFFFF});
+  ASSERT_TRUE(transformation7.IsApplicable(context.get(), fact_manager));
+  transformation7.Apply(context.get(), &fact_manager);
+  temp_dd = MakeDataDescriptor(206, {1});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(56, {}), temp_dd,
+                                        context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypeVector %6 2
+         %10 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %12 = OpConstantComposite %7 %10 %11
+        %112 = OpUndef %7
+         %13 = OpTypeVector %6 3
+         %16 = OpConstantComposite %13 %10 %11 %10
+         %17 = OpTypeVector %6 4
+         %20 = OpConstantComposite %17 %10 %11 %10 %11
+         %21 = OpTypeInt 32 1
+         %22 = OpTypeVector %21 2
+         %25 = OpConstant %21 1
+         %26 = OpConstant %21 0
+         %27 = OpConstantComposite %22 %25 %26
+         %28 = OpTypeVector %21 3
+         %31 = OpConstantComposite %28 %25 %26 %25
+         %32 = OpTypeVector %21 4
+         %33 = OpTypePointer Function %32
+         %35 = OpConstantComposite %32 %25 %26 %25 %26
+         %36 = OpTypeInt 32 0
+         %37 = OpTypeVector %36 2
+         %40 = OpConstant %36 1
+         %41 = OpConstant %36 0
+         %42 = OpConstantComposite %37 %40 %41
+         %43 = OpTypeVector %36 3
+         %46 = OpConstantComposite %43 %40 %41 %40
+         %47 = OpTypeVector %36 4
+         %50 = OpConstantComposite %47 %40 %41 %40 %41
+         %51 = OpTypeFloat 32
+         %55 = OpConstant %51 1
+         %56 = OpConstant %51 0
+         %58 = OpTypeVector %51 3
+         %61 = OpConstantComposite %58 %55 %56 %55
+         %62 = OpTypeVector %51 4
+         %65 = OpConstantComposite %62 %55 %56 %55 %56
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %100 None
+               OpBranchConditional %10 %101 %102
+        %101 = OpLabel
+        %103 = OpCompositeConstruct %62 %55 %55 %55 %56
+               OpBranch %100
+        %102 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+        %200 = OpVectorShuffle %7 %12 %112 1 0
+        %201 = OpVectorShuffle %13 %20 %12 0xFFFFFFFF 3 5
+        %202 = OpVectorShuffle %28 %27 %35 5 4 1
+        %203 = OpVectorShuffle %37 %42 %46 0 1
+        %204 = OpVectorShuffle %43 %42 %46 2 3 4
+        %205 = OpVectorShuffle %47 %42 %42 0 1 2 3
+        %206 = OpVectorShuffle %62 %65 %65 0xFFFFFFFF 3 6 0xFFFFFFFF
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationVectorShuffleTest, IllegalInsertionPoints) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %51 %27
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %25 "buf"
+               OpMemberName %25 0 "value"
+               OpName %27 ""
+               OpName %51 "color"
+               OpMemberDecorate %25 0 Offset 0
+               OpDecorate %25 Block
+               OpDecorate %27 DescriptorSet 0
+               OpDecorate %27 Binding 0
+               OpDecorate %51 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+        %150 = OpTypeVector %6 2
+         %10 = OpConstant %6 0.300000012
+         %11 = OpConstant %6 0.400000006
+         %12 = OpConstant %6 0.5
+         %13 = OpConstant %6 1
+         %14 = OpConstantComposite %7 %10 %11 %12 %13
+         %15 = OpTypeInt 32 1
+         %18 = OpConstant %15 0
+         %25 = OpTypeStruct %6
+         %26 = OpTypePointer Uniform %25
+         %27 = OpVariable %26 Uniform
+         %28 = OpTypePointer Uniform %6
+         %32 = OpTypeBool
+        %103 = OpConstantTrue %32
+         %34 = OpConstant %6 0.100000001
+         %48 = OpConstant %15 1
+         %50 = OpTypePointer Output %7
+         %51 = OpVariable %50 Output
+        %100 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %101 = OpVariable %100 Function
+        %102 = OpVariable %100 Function
+               OpBranch %19
+         %19 = OpLabel
+         %60 = OpPhi %7 %14 %5 %58 %20
+         %59 = OpPhi %15 %18 %5 %49 %20
+         %29 = OpAccessChain %28 %27 %18
+         %30 = OpLoad %6 %29
+         %31 = OpConvertFToS %15 %30
+         %33 = OpSLessThan %32 %59 %31
+               OpLoopMerge %21 %20 None
+               OpBranchConditional %33 %20 %21
+         %20 = OpLabel
+         %39 = OpCompositeExtract %6 %60 0
+         %40 = OpFAdd %6 %39 %34
+         %55 = OpCompositeInsert %7 %40 %60 0
+         %44 = OpCompositeExtract %6 %60 1
+         %45 = OpFSub %6 %44 %34
+         %58 = OpCompositeInsert %7 %45 %55 1
+         %49 = OpIAdd %15 %59 %48
+               OpBranch %19
+         %21 = OpLabel
+               OpStore %51 %60
+               OpSelectionMerge %105 None
+               OpBranchConditional %103 %104 %105
+        %104 = OpLabel
+               OpBranch %105
+        %105 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+
+  // Cannot insert before the OpVariables of a function.
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(101, SpvOpVariable, 0), 200, 14, 14, {0, 1})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(101, SpvOpVariable, 1), 200, 14, 14, {1, 2})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(102, SpvOpVariable, 0), 200, 14, 14, {1, 2})
+          .IsApplicable(context.get(), fact_manager));
+  // OK to insert right after the OpVariables.
+  ASSERT_FALSE(
+      TransformationVectorShuffle(
+          MakeInstructionDescriptor(102, SpvOpBranch, 1), 200, 14, 14, {1, 1})
+          .IsApplicable(context.get(), fact_manager));
+
+  // Cannot insert before the OpPhis of a block.
+  ASSERT_FALSE(
+      TransformationVectorShuffle(MakeInstructionDescriptor(60, SpvOpPhi, 0),
+                                  200, 14, 14, {2, 0})
+          .IsApplicable(context.get(), fact_manager));
+  ASSERT_FALSE(
+      TransformationVectorShuffle(MakeInstructionDescriptor(59, SpvOpPhi, 0),
+                                  200, 14, 14, {3, 0})
+          .IsApplicable(context.get(), fact_manager));
+  // OK to insert after the OpPhis.
+  ASSERT_TRUE(TransformationVectorShuffle(
+                  MakeInstructionDescriptor(59, SpvOpAccessChain, 0), 200, 14,
+                  14, {3, 4})
+                  .IsApplicable(context.get(), fact_manager));
+
+  // Cannot insert before OpLoopMerge
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(33, SpvOpBranchConditional, 0),
+                   200, 14, 14, {3})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Cannot insert before OpSelectionMerge
+  ASSERT_FALSE(TransformationVectorShuffle(
+                   MakeInstructionDescriptor(21, SpvOpBranchConditional, 0),
+                   200, 14, 14, {2})
+                   .IsApplicable(context.get(), fact_manager));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools

+ 12 - 17
3rdparty/spirv-tools/test/val/val_atomics_test.cpp

@@ -499,9 +499,8 @@ TEST_F(ValidateAtomics, AtomicLoadWrongScopeType) {
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicLoad: expected Memory Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicLoad: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicLoadWrongMemorySemanticsType) {
@@ -674,10 +673,9 @@ OpAtomicStore %f32_var %f32_1 %relaxed %f32_1
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicStore: expected Memory Scope to be a 32-bit int\n  "
-                "OpAtomicStore %28 %float_1 %uint_0_1 %float_1\n"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicStore: expected scope to be a 32-bit int\n  "
+                        "OpAtomicStore %28 %float_1 %uint_0_1 %float_1\n"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongMemorySemanticsType) {
@@ -788,9 +786,8 @@ OpAtomicStore %f32_var %device %relaxed %f32_1
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicExchange: expected Memory Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicExchange: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicExchangeWrongMemorySemanticsType) {
@@ -902,10 +899,9 @@ OpAtomicStore %f32_var %device %relaxed %f32_1
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("AtomicCompareExchange: expected Memory Scope to be a 32-bit "
-                "int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicCompareExchange: expected scope to be a 32-bit "
+                        "int"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType1) {
@@ -1085,8 +1081,7 @@ TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongScopeType) {
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "AtomicFlagTestAndSet: expected Memory Scope to be a 32-bit int"));
+      HasSubstr("AtomicFlagTestAndSet: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongMemorySemanticsType) {
@@ -1159,7 +1154,7 @@ OpAtomicFlagClear %u32_var %u64_1 %relaxed
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("AtomicFlagClear: expected Memory Scope to be a 32-bit "
+              HasSubstr("AtomicFlagClear: expected scope to be a 32-bit "
                         "int\n  OpAtomicFlagClear %30 %ulong_1 %uint_0_1\n"));
 }
 

+ 13 - 20
3rdparty/spirv-tools/test/val/val_barriers_test.cpp

@@ -341,9 +341,8 @@ OpControlBarrier %f32_1 %device %none
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("ControlBarrier: expected Execution Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ControlBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierU64ExecutionScope) {
@@ -353,9 +352,8 @@ OpControlBarrier %u64_1 %device %none
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("ControlBarrier: expected Execution Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ControlBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierFloatMemoryScope) {
@@ -365,9 +363,8 @@ OpControlBarrier %device %f32_1 %none
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("ControlBarrier: expected Memory Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ControlBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierU64MemoryScope) {
@@ -377,9 +374,8 @@ OpControlBarrier %device %u64_1 %none
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("ControlBarrier: expected Memory Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ControlBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierFloatMemorySemantics) {
@@ -797,9 +793,8 @@ OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("MemoryBarrier: expected Memory Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MemoryBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpMemoryBarrierU64MemoryScope) {
@@ -809,9 +804,8 @@ OpMemoryBarrier %u64_1 %acquire_release_uniform_workgroup
 
   CompileSuccessfully(GenerateShaderCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("MemoryBarrier: expected Memory Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("MemoryBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemorySemantics) {
@@ -993,8 +987,7 @@ OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_workgroup
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr(
-          "MemoryNamedBarrier: expected Memory Scope to be a 32-bit int"));
+      HasSubstr("MemoryNamedBarrier: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemorySemantics) {

+ 2 - 3
3rdparty/spirv-tools/test/val/val_decoration_test.cpp

@@ -4861,9 +4861,8 @@ TEST_F(ValidateDecorations,
   CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("ConstantNull: expected Execution Scope to be a 32-bit int"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ConstantNull: expected scope to be a 32-bit int"));
 }
 
 TEST_F(ValidateDecorations,

+ 65 - 4
3rdparty/spirv-tools/test/val/val_misc_test.cpp

@@ -144,19 +144,18 @@ OpFunctionEnd)";
   EXPECT_THAT(getDiagnosticString(), HasSubstr("vector of two components"));
 }
 
-// #2952: disabled until scope discussion is resolved.
-TEST_F(ValidateMisc, DISABLED_ShaderClockExecutionScope) {
+TEST_F(ValidateMisc, ShaderClockInvalidScopeValue) {
   const std::string spirv = ShaderClockSpriv + R"(
 %3 = OpTypeFunction %void
 %ulong = OpTypeInt 64 0
 %uint = OpTypeInt 32 0
 %_ptr_Function_ulong = OpTypePointer Function %ulong
-%uint_3 = OpConstant %uint 10
+%uint_10 = OpConstant %uint 10
 %uint_1 = OpConstant %uint 1
 %main = OpFunction %void None %3
 %5 = OpLabel
 %time1 = OpVariable %_ptr_Function_ulong Function
-%11 = OpReadClockKHR %ulong %uint_3
+%11 = OpReadClockKHR %ulong %uint_10
 OpStore %time1 %11
 OpReturn
 OpFunctionEnd)";
@@ -165,6 +164,68 @@ OpFunctionEnd)";
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(), HasSubstr("Invalid scope value"));
 }
+
+TEST_F(ValidateMisc, ShaderClockSubgroupScope) {
+  const std::string spirv = ShaderClockSpriv + R"(
+%3 = OpTypeFunction %void
+%ulong = OpTypeInt 64 0
+%uint = OpTypeInt 32 0
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+%subgroup = OpConstant %uint 3
+%uint_1 = OpConstant %uint 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%time1 = OpVariable %_ptr_Function_ulong Function
+%11 = OpReadClockKHR %ulong %subgroup
+OpStore %time1 %11
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMisc, ShaderClockDeviceScope) {
+  const std::string spirv = ShaderClockSpriv + R"(
+%3 = OpTypeFunction %void
+%ulong = OpTypeInt 64 0
+%uint = OpTypeInt 32 0
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+%device = OpConstant %uint 1
+%uint_1 = OpConstant %uint 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%time1 = OpVariable %_ptr_Function_ulong Function
+%11 = OpReadClockKHR %ulong %device
+OpStore %time1 %11
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateMisc, ShaderClockWorkgroupScope) {
+  const std::string spirv = ShaderClockSpriv + R"(
+%3 = OpTypeFunction %void
+%ulong = OpTypeInt 64 0
+%uint = OpTypeInt 32 0
+%_ptr_Function_ulong = OpTypePointer Function %ulong
+%workgroup = OpConstant %uint 2
+%uint_1 = OpConstant %uint 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%time1 = OpVariable %_ptr_Function_ulong Function
+%11 = OpReadClockKHR %ulong %workgroup
+OpStore %time1 %11
+OpReturn
+OpFunctionEnd)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Scope must be Subgroup or Device"));
+}
 }  // namespace
 }  // namespace val
 }  // namespace spvtools

+ 1 - 1
3rdparty/spirv-tools/utils/update_build_version.py

@@ -86,7 +86,7 @@ def deduce_software_version(directory):
     # Linux.
     pattern = re.compile(r'^(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d\s*$')
     changes_file = os.path.join(directory, 'CHANGES')
-    with open(changes_file, mode='rU') as f:
+    with open(changes_file, mode='r') as f:
         for line in f.readlines():
             match = pattern.match(line)
             if match:

+ 7 - 1
3rdparty/spirv-tools/utils/vscode/src/grammar/grammar.go

@@ -66,10 +66,16 @@ type Parameter struct {
 	Name string `json:"name"`
 }
 
+// Quantifier indicates the number of times the quantified term may appear.
 type Quantifier string
 
 const (
-	Once       Quantifier = ""
+	// Once indicates the quantified term may appear exactly once.
+	Once Quantifier = ""
+	// ZeroOrOnce indicates the quantified term may appear zero or one
+	// time; an optional term.
 	ZeroOrOnce Quantifier = "?"
+	// ZeroOrMany indicates the quantified term may appear any number of
+	// times.
 	ZeroOrMany Quantifier = "*"
 )