Browse Source

Updated spirv-tools.

Бранимир Караџић 5 years ago
parent
commit
885a86cf56
51 changed files with 2877 additions and 1076 deletions
  1. 1 1
      3rdparty/spirv-tools/include/generated/build-version.inc
  2. 24 2
      3rdparty/spirv-tools/source/fuzz/CMakeLists.txt
  3. 30 0
      3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.cpp
  4. 45 0
      3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.h
  5. 235 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.cpp
  6. 90 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.h
  7. 63 805
      3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp
  8. 156 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h
  9. 31 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.cpp
  10. 44 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.h
  11. 244 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.cpp
  12. 16 35
      3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.h
  13. 51 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.cpp
  14. 52 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.h
  15. 32 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.cpp
  16. 44 0
      3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.h
  17. 1 4
      3rdparty/spirv-tools/source/fuzz/force_render_red.cpp
  18. 36 49
      3rdparty/spirv-tools/source/fuzz/fuzzer.cpp
  19. 25 5
      3rdparty/spirv-tools/source/fuzz/fuzzer.h
  20. 6 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp
  21. 8 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_context.h
  22. 0 2
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass.cpp
  23. 297 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
  24. 73 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
  25. 104 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.cpp
  26. 41 0
      3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.h
  27. 27 39
      3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp
  28. 23 0
      3rdparty/spirv-tools/source/fuzz/overflow_id_source.cpp
  29. 106 0
      3rdparty/spirv-tools/source/fuzz/overflow_id_source.h
  30. 34 0
      3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto
  31. 47 54
      3rdparty/spirv-tools/source/fuzz/replayer.cpp
  32. 25 7
      3rdparty/spirv-tools/source/fuzz/replayer.h
  33. 41 41
      3rdparty/spirv-tools/source/fuzz/shrinker.cpp
  34. 21 4
      3rdparty/spirv-tools/source/fuzz/shrinker.h
  35. 8 0
      3rdparty/spirv-tools/source/fuzz/transformation.cpp
  36. 200 0
      3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.cpp
  37. 72 0
      3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.h
  38. 32 1
      3rdparty/spirv-tools/source/fuzz/transformation_context.cpp
  39. 20 2
      3rdparty/spirv-tools/source/fuzz/transformation_context.h
  40. 281 0
      3rdparty/spirv-tools/source/fuzz/transformation_inline_function.cpp
  41. 75 0
      3rdparty/spirv-tools/source/fuzz/transformation_inline_function.h
  42. 27 6
      3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp
  43. 0 1
      3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h
  44. 38 11
      3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp
  45. 7 0
      3rdparty/spirv-tools/source/opt/debug_info_manager.cpp
  46. 8 0
      3rdparty/spirv-tools/source/opt/inline_pass.cpp
  47. 4 2
      3rdparty/spirv-tools/source/opt/instruction.cpp
  48. 9 0
      3rdparty/spirv-tools/source/opt/instruction.h
  49. 3 5
      3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp
  50. 13 0
      3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp
  51. 7 0
      3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h

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

@@ -1 +1 @@
-"v2020.5", "SPIRV-Tools v2020.5 46c9719f1b46f358df41b11c2575182cb5befbd5"
+"v2020.5", "SPIRV-Tools v2020.5 e272a31e19b278158551bc64142947df09ede4ac"

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

@@ -30,9 +30,15 @@ if(SPIRV_BUILD_FUZZER)
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
         call_graph.h
+        counter_overflow_id_source.h
         data_descriptor.h
         equivalence_relation.h
-        fact_manager.h
+        fact_manager/constant_uniform_facts.h
+        fact_manager/data_synonym_and_id_equation_facts.h
+        fact_manager/dead_block_facts.h
+        fact_manager/fact_manager.h
+        fact_manager/irrelevant_value_facts.h
+        fact_manager/livesafe_function_facts.h
         force_render_red.h
         fuzzer.h
         fuzzer_context.h
@@ -53,6 +59,7 @@ if(SPIRV_BUILD_FUZZER)
         fuzzer_pass_add_local_variables.h
         fuzzer_pass_add_loop_preheaders.h
         fuzzer_pass_add_no_contraction_decorations.h
+        fuzzer_pass_add_opphi_synonyms.h
         fuzzer_pass_add_parameters.h
         fuzzer_pass_add_relaxed_decorations.h
         fuzzer_pass_add_stores.h
@@ -66,6 +73,7 @@ if(SPIRV_BUILD_FUZZER)
         fuzzer_pass_construct_composites.h
         fuzzer_pass_copy_objects.h
         fuzzer_pass_donate_modules.h
+        fuzzer_pass_inline_functions.h
         fuzzer_pass_invert_comparison_operators.h
         fuzzer_pass_interchange_signedness_of_integer_operands.h
         fuzzer_pass_interchange_zero_like_constants.h
@@ -94,6 +102,7 @@ if(SPIRV_BUILD_FUZZER)
         id_use_descriptor.h
         instruction_descriptor.h
         instruction_message.h
+        overflow_id_source.h
         protobufs/spirvfuzz_protobufs.h
         pseudo_random_generator.h
         random_generator.h
@@ -116,6 +125,7 @@ if(SPIRV_BUILD_FUZZER)
         transformation_add_local_variable.h
         transformation_add_loop_preheader.h
         transformation_add_no_contraction_decoration.h
+        transformation_add_opphi_synonym.h
         transformation_add_parameter.h
         transformation_add_relaxed_decoration.h
         transformation_add_spec_constant_op.h
@@ -137,6 +147,7 @@ if(SPIRV_BUILD_FUZZER)
         transformation_context.h
         transformation_equation_instruction.h
         transformation_function_call.h
+        transformation_inline_function.h
         transformation_invert_comparison_operator.h
         transformation_load.h
         transformation_make_vector_operation_dynamic.h
@@ -173,8 +184,14 @@ if(SPIRV_BUILD_FUZZER)
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
         call_graph.cpp
+        counter_overflow_id_source.cpp
         data_descriptor.cpp
-        fact_manager.cpp
+        fact_manager/constant_uniform_facts.cpp
+        fact_manager/data_synonym_and_id_equation_facts.cpp
+        fact_manager/dead_block_facts.cpp
+        fact_manager/fact_manager.cpp
+        fact_manager/irrelevant_value_facts.cpp
+        fact_manager/livesafe_function_facts.cpp
         force_render_red.cpp
         fuzzer.cpp
         fuzzer_context.cpp
@@ -195,6 +212,7 @@ if(SPIRV_BUILD_FUZZER)
         fuzzer_pass_add_local_variables.cpp
         fuzzer_pass_add_loop_preheaders.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
+        fuzzer_pass_add_opphi_synonyms.cpp
         fuzzer_pass_add_parameters.cpp
         fuzzer_pass_add_relaxed_decorations.cpp
         fuzzer_pass_add_stores.cpp
@@ -208,6 +226,7 @@ if(SPIRV_BUILD_FUZZER)
         fuzzer_pass_construct_composites.cpp
         fuzzer_pass_copy_objects.cpp
         fuzzer_pass_donate_modules.cpp
+        fuzzer_pass_inline_functions.cpp
         fuzzer_pass_invert_comparison_operators.cpp
         fuzzer_pass_interchange_signedness_of_integer_operands.cpp
         fuzzer_pass_interchange_zero_like_constants.cpp
@@ -236,6 +255,7 @@ if(SPIRV_BUILD_FUZZER)
         id_use_descriptor.cpp
         instruction_descriptor.cpp
         instruction_message.cpp
+        overflow_id_source.cpp
         pseudo_random_generator.cpp
         random_generator.cpp
         replayer.cpp
@@ -257,6 +277,7 @@ if(SPIRV_BUILD_FUZZER)
         transformation_add_local_variable.cpp
         transformation_add_loop_preheader.cpp
         transformation_add_no_contraction_decoration.cpp
+        transformation_add_opphi_synonym.cpp
         transformation_add_parameter.cpp
         transformation_add_relaxed_decoration.cpp
         transformation_add_spec_constant_op.cpp
@@ -278,6 +299,7 @@ if(SPIRV_BUILD_FUZZER)
         transformation_context.cpp
         transformation_equation_instruction.cpp
         transformation_function_call.cpp
+        transformation_inline_function.cpp
         transformation_invert_comparison_operator.cpp
         transformation_load.cpp
         transformation_make_vector_operation_dynamic.cpp

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

@@ -0,0 +1,30 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/counter_overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+CounterOverflowIdSource::CounterOverflowIdSource(uint32_t first_available_id)
+    : next_available_id_(first_available_id) {}
+
+bool CounterOverflowIdSource::HasOverflowIds() const { return true; }
+
+uint32_t CounterOverflowIdSource::GetNextOverflowId() {
+  return next_available_id_++;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 45 - 0
3rdparty/spirv-tools/source/fuzz/counter_overflow_id_source.h

@@ -0,0 +1,45 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_
+#define SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_
+
+#include "source/fuzz/overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A source of overflow ids that uses a counter to provide successive ids from
+// a given starting value.
+class CounterOverflowIdSource : public OverflowIdSource {
+ public:
+  // |first_available_id| is the starting value for the counter.
+  explicit CounterOverflowIdSource(uint32_t first_available_id);
+
+  // Always returns true.
+  bool HasOverflowIds() const override;
+
+  // Returns the current counter value and increments the counter.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) We should
+  //  account for the case where the maximum allowed id is reached.
+  uint32_t GetNextOverflowId() override;
+
+ private:
+  uint32_t next_available_id_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_COUNTER_H_

+ 235 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.cpp

@@ -0,0 +1,235 @@
+// 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/fact_manager/constant_uniform_facts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+uint32_t ConstantUniformFacts::GetConstantId(
+    opt::IRContext* context,
+    const protobufs::FactConstantUniform& constant_uniform_fact,
+    uint32_t type_id) {
+  auto type = context->get_type_mgr()->GetType(type_id);
+  assert(type != nullptr && "Unknown type id.");
+  const opt::analysis::Constant* known_constant;
+  if (type->AsInteger()) {
+    opt::analysis::IntConstant candidate_constant(
+        type->AsInteger(), GetConstantWords(constant_uniform_fact));
+    known_constant =
+        context->get_constant_mgr()->FindConstant(&candidate_constant);
+  } else {
+    assert(
+        type->AsFloat() &&
+        "Uniform constant facts are only supported for int and float types.");
+    opt::analysis::FloatConstant candidate_constant(
+        type->AsFloat(), GetConstantWords(constant_uniform_fact));
+    known_constant =
+        context->get_constant_mgr()->FindConstant(&candidate_constant);
+  }
+  if (!known_constant) {
+    return 0;
+  }
+  return context->get_constant_mgr()->FindDeclaredConstant(known_constant,
+                                                           type_id);
+}
+
+std::vector<uint32_t> ConstantUniformFacts::GetConstantWords(
+    const protobufs::FactConstantUniform& constant_uniform_fact) {
+  std::vector<uint32_t> result;
+  for (auto constant_word : constant_uniform_fact.constant_word()) {
+    result.push_back(constant_word);
+  }
+  return result;
+}
+
+bool ConstantUniformFacts::DataMatches(
+    const opt::Instruction& constant_instruction,
+    const protobufs::FactConstantUniform& constant_uniform_fact) {
+  assert(constant_instruction.opcode() == SpvOpConstant);
+  std::vector<uint32_t> data_in_constant;
+  for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
+    data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
+  }
+  return data_in_constant == GetConstantWords(constant_uniform_fact);
+}
+
+std::vector<uint32_t>
+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_) {
+    if (fact_and_type_id.second != type_id) {
+      continue;
+    }
+    if (auto constant_id =
+            GetConstantId(ir_context, fact_and_type_id.first, type_id)) {
+      if (already_seen.find(constant_id) == already_seen.end()) {
+        result.push_back(constant_id);
+        already_seen.insert(constant_id);
+      }
+    }
+  }
+  return result;
+}
+
+std::vector<protobufs::UniformBufferElementDescriptor>
+ConstantUniformFacts::GetUniformDescriptorsForConstant(
+    opt::IRContext* ir_context, uint32_t constant_id) const {
+  std::vector<protobufs::UniformBufferElementDescriptor> result;
+  auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id);
+  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_) {
+    if (fact_and_type_id.second != type_id) {
+      continue;
+    }
+    if (DataMatches(*constant_inst, fact_and_type_id.first)) {
+      result.emplace_back(
+          fact_and_type_id.first.uniform_buffer_element_descriptor());
+    }
+  }
+  return result;
+}
+
+uint32_t ConstantUniformFacts::GetConstantFromUniformDescriptor(
+    opt::IRContext* context,
+    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+  // Consider each fact.
+  for (auto& fact_and_type : facts_and_type_ids_) {
+    // Check whether the uniform descriptor associated with the fact matches
+    // |uniform_descriptor|.
+    if (UniformBufferElementDescriptorEquals()(
+            &uniform_descriptor,
+            &fact_and_type.first.uniform_buffer_element_descriptor())) {
+      return GetConstantId(context, fact_and_type.first, fact_and_type.second);
+    }
+  }
+  // No fact associated with the given uniform descriptor was found.
+  return 0;
+}
+
+std::vector<uint32_t>
+ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() const {
+  std::vector<uint32_t> result;
+  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);
+    }
+  }
+  return result;
+}
+
+bool ConstantUniformFacts::FloatingPointValueIsSuitable(
+    const protobufs::FactConstantUniform& fact, uint32_t width) {
+  const uint32_t kFloatWidth = 32;
+  const uint32_t kDoubleWidth = 64;
+  if (width != kFloatWidth && width != kDoubleWidth) {
+    // Only 32- and 64-bit floating-point types are handled.
+    return false;
+  }
+  std::vector<uint32_t> words = GetConstantWords(fact);
+  if (width == 32) {
+    float value;
+    memcpy(&value, words.data(), sizeof(float));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  } else {
+    double value;
+    memcpy(&value, words.data(), sizeof(double));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ConstantUniformFacts::AddFact(const protobufs::FactConstantUniform& fact,
+                                   opt::IRContext* context) {
+  // Try to find a unique instruction that declares a variable such that the
+  // variable is decorated with the descriptor set and binding associated with
+  // the constant uniform fact.
+  opt::Instruction* uniform_variable = FindUniformVariable(
+      fact.uniform_buffer_element_descriptor(), context, true);
+
+  if (!uniform_variable) {
+    return false;
+  }
+
+  assert(SpvOpVariable == uniform_variable->opcode());
+  assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
+
+  auto should_be_uniform_pointer_type =
+      context->get_type_mgr()->GetType(uniform_variable->type_id());
+  if (!should_be_uniform_pointer_type->AsPointer()) {
+    return false;
+  }
+  if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
+      SpvStorageClassUniform) {
+    return false;
+  }
+  auto should_be_uniform_pointer_instruction =
+      context->get_def_use_mgr()->GetDef(uniform_variable->type_id());
+  auto composite_type =
+      should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
+
+  auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices(
+      context, composite_type,
+      fact.uniform_buffer_element_descriptor().index());
+  if (!final_element_type_id) {
+    return false;
+  }
+  auto final_element_type =
+      context->get_type_mgr()->GetType(final_element_type_id);
+  assert(final_element_type &&
+         "There should be a type corresponding to this id.");
+
+  if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
+    return false;
+  }
+  auto width = final_element_type->AsFloat()
+                   ? final_element_type->AsFloat()->width()
+                   : final_element_type->AsInteger()->width();
+
+  if (final_element_type->AsFloat() &&
+      !FloatingPointValueIsSuitable(fact, width)) {
+    return false;
+  }
+
+  auto required_words = (width + 32 - 1) / 32;
+  if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
+    return false;
+  }
+  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>>&
+ConstantUniformFacts::GetConstantUniformFactsAndTypes() const {
+  return facts_and_type_ids_;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools

+ 90 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/constant_uniform_facts.h

@@ -0,0 +1,90 @@
+// 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_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
+
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about uniform constants.
+class ConstantUniformFacts {
+ public:
+  // See method in FactManager which delegates to this method.
+  bool AddFact(const protobufs::FactConstantUniform& fact,
+               opt::IRContext* context);
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
+      opt::IRContext* ir_context, uint32_t type_id) const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<protobufs::UniformBufferElementDescriptor>
+  GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+                                   uint32_t constant_id) const;
+
+  // See method in FactManager which delegates to this method.
+  uint32_t GetConstantFromUniformDescriptor(
+      opt::IRContext* context,
+      const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
+      const;
+
+  // 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|.
+  static bool DataMatches(
+      const opt::Instruction& constant_instruction,
+      const protobufs::FactConstantUniform& constant_uniform_fact);
+
+  // Yields the constant words associated with |constant_uniform_fact|.
+  static std::vector<uint32_t> GetConstantWords(
+      const protobufs::FactConstantUniform& constant_uniform_fact);
+
+  // Yields the id of a constant of type |type_id| whose data matches the
+  // constant data in |constant_uniform_fact|, or 0 if no such constant is
+  // declared.
+  static uint32_t GetConstantId(
+      opt::IRContext* context,
+      const protobufs::FactConstantUniform& constant_uniform_fact,
+      uint32_t type_id);
+
+  // Checks that the width of a floating-point constant is supported, and that
+  // the constant is finite.
+  static bool FloatingPointValueIsSuitable(
+      const protobufs::FactConstantUniform& fact, uint32_t width);
+
+  std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
+      facts_and_type_ids_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_

+ 63 - 805
3rdparty/spirv-tools/source/fuzz/fact_manager.cpp → 3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp

@@ -12,529 +12,60 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
 
-#include <sstream>
-#include <unordered_map>
-#include <unordered_set>
-
-#include "source/fuzz/equivalence_relation.h"
 #include "source/fuzz/fuzzer_util.h"
-#include "source/fuzz/uniform_buffer_element_descriptor.h"
-#include "source/opt/ir_context.h"
 
 namespace spvtools {
 namespace fuzz {
+namespace fact_manager {
 
-namespace {
-
-std::string ToString(const protobufs::FactConstantUniform& fact) {
-  std::stringstream stream;
-  stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set()
-         << ", " << fact.uniform_buffer_element_descriptor().binding() << ")[";
-
-  bool first = true;
-  for (auto index : fact.uniform_buffer_element_descriptor().index()) {
-    if (first) {
-      first = false;
-    } else {
-      stream << ", ";
-    }
-    stream << index;
-  }
-
-  stream << "] == [";
-
-  first = true;
-  for (auto constant_word : fact.constant_word()) {
-    if (first) {
-      first = false;
-    } else {
-      stream << ", ";
-    }
-    stream << constant_word;
-  }
-
-  stream << "]";
-  return stream.str();
-}
-
-std::string ToString(const protobufs::FactDataSynonym& fact) {
-  std::stringstream stream;
-  stream << fact.data1() << " = " << fact.data2();
-  return stream.str();
-}
-
-std::string ToString(const protobufs::FactIdEquation& fact) {
-  std::stringstream stream;
-  stream << fact.lhs_id();
-  stream << " " << static_cast<SpvOp>(fact.opcode());
-  for (auto rhs_id : fact.rhs_id()) {
-    stream << " " << rhs_id;
-  }
-  return stream.str();
-}
-
-std::string ToString(const protobufs::Fact& fact) {
-  switch (fact.fact_case()) {
-    case protobufs::Fact::kConstantUniformFact:
-      return ToString(fact.constant_uniform_fact());
-    case protobufs::Fact::kDataSynonymFact:
-      return ToString(fact.data_synonym_fact());
-    case protobufs::Fact::kIdEquationFact:
-      return ToString(fact.id_equation_fact());
-    default:
-      assert(false && "Stringification not supported for this fact.");
-      return "";
-  }
-}
-
-}  // namespace
-
-//=======================
-// Constant uniform facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about uniform constants.
-class FactManager::ConstantUniformFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  bool AddFact(const protobufs::FactConstantUniform& fact,
-               opt::IRContext* context);
-
-  // See method in FactManager which delegates to this method.
-  std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
-      opt::IRContext* ir_context, uint32_t type_id) const;
-
-  // See method in FactManager which delegates to this method.
-  const std::vector<protobufs::UniformBufferElementDescriptor>
-  GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
-                                   uint32_t constant_id) const;
-
-  // See method in FactManager which delegates to this method.
-  uint32_t GetConstantFromUniformDescriptor(
-      opt::IRContext* context,
-      const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
-      const;
-
-  // 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|.
-  bool DataMatches(
-      const opt::Instruction& constant_instruction,
-      const protobufs::FactConstantUniform& constant_uniform_fact) const;
-
-  // Yields the constant words associated with |constant_uniform_fact|.
-  std::vector<uint32_t> GetConstantWords(
-      const protobufs::FactConstantUniform& constant_uniform_fact) const;
-
-  // Yields the id of a constant of type |type_id| whose data matches the
-  // constant data in |constant_uniform_fact|, or 0 if no such constant is
-  // declared.
-  uint32_t GetConstantId(
-      opt::IRContext* context,
-      const protobufs::FactConstantUniform& constant_uniform_fact,
-      uint32_t type_id) const;
-
-  // Checks that the width of a floating-point constant is supported, and that
-  // the constant is finite.
-  bool FloatingPointValueIsSuitable(const protobufs::FactConstantUniform& fact,
-                                    uint32_t width) const;
-
-  std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
-      facts_and_type_ids_;
-};
-
-uint32_t FactManager::ConstantUniformFacts::GetConstantId(
-    opt::IRContext* context,
-    const protobufs::FactConstantUniform& constant_uniform_fact,
-    uint32_t type_id) const {
-  auto type = context->get_type_mgr()->GetType(type_id);
-  assert(type != nullptr && "Unknown type id.");
-  const opt::analysis::Constant* known_constant;
-  if (type->AsInteger()) {
-    opt::analysis::IntConstant candidate_constant(
-        type->AsInteger(), GetConstantWords(constant_uniform_fact));
-    known_constant =
-        context->get_constant_mgr()->FindConstant(&candidate_constant);
-  } else {
-    assert(
-        type->AsFloat() &&
-        "Uniform constant facts are only supported for int and float types.");
-    opt::analysis::FloatConstant candidate_constant(
-        type->AsFloat(), GetConstantWords(constant_uniform_fact));
-    known_constant =
-        context->get_constant_mgr()->FindConstant(&candidate_constant);
-  }
-  if (!known_constant) {
-    return 0;
-  }
-  return context->get_constant_mgr()->FindDeclaredConstant(known_constant,
-                                                           type_id);
-}
-
-std::vector<uint32_t> FactManager::ConstantUniformFacts::GetConstantWords(
-    const protobufs::FactConstantUniform& constant_uniform_fact) const {
-  std::vector<uint32_t> result;
-  for (auto constant_word : constant_uniform_fact.constant_word()) {
-    result.push_back(constant_word);
-  }
-  return result;
-}
-
-bool FactManager::ConstantUniformFacts::DataMatches(
-    const opt::Instruction& constant_instruction,
-    const protobufs::FactConstantUniform& constant_uniform_fact) const {
-  assert(constant_instruction.opcode() == SpvOpConstant);
-  std::vector<uint32_t> data_in_constant;
-  for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
-    data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
-  }
-  return data_in_constant == GetConstantWords(constant_uniform_fact);
-}
-
-std::vector<uint32_t>
-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_) {
-    if (fact_and_type_id.second != type_id) {
-      continue;
-    }
-    if (auto constant_id =
-            GetConstantId(ir_context, fact_and_type_id.first, type_id)) {
-      if (already_seen.find(constant_id) == already_seen.end()) {
-        result.push_back(constant_id);
-        already_seen.insert(constant_id);
-      }
-    }
-  }
-  return result;
-}
-
-const std::vector<protobufs::UniformBufferElementDescriptor>
-FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant(
-    opt::IRContext* ir_context, uint32_t constant_id) const {
-  std::vector<protobufs::UniformBufferElementDescriptor> result;
-  auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id);
-  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_) {
-    if (fact_and_type_id.second != type_id) {
-      continue;
-    }
-    if (DataMatches(*constant_inst, fact_and_type_id.first)) {
-      result.emplace_back(
-          fact_and_type_id.first.uniform_buffer_element_descriptor());
-    }
-  }
-  return result;
-}
-
-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_) {
-    // Check whether the uniform descriptor associated with the fact matches
-    // |uniform_descriptor|.
-    if (UniformBufferElementDescriptorEquals()(
-            &uniform_descriptor,
-            &fact_and_type.first.uniform_buffer_element_descriptor())) {
-      return GetConstantId(context, fact_and_type.first, fact_and_type.second);
-    }
-  }
-  // No fact associated with the given uniform descriptor was found.
-  return 0;
-}
-
-std::vector<uint32_t>
-FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown()
-    const {
-  std::vector<uint32_t> result;
-  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);
-    }
-  }
-  return result;
-}
-
-bool FactManager::ConstantUniformFacts::FloatingPointValueIsSuitable(
-    const protobufs::FactConstantUniform& fact, uint32_t width) const {
-  const uint32_t kFloatWidth = 32;
-  const uint32_t kDoubleWidth = 64;
-  if (width != kFloatWidth && width != kDoubleWidth) {
-    // Only 32- and 64-bit floating-point types are handled.
-    return false;
-  }
-  std::vector<uint32_t> words = GetConstantWords(fact);
-  if (width == 32) {
-    float value;
-    memcpy(&value, words.data(), sizeof(float));
-    if (!std::isfinite(value)) {
-      return false;
-    }
-  } else {
-    double value;
-    memcpy(&value, words.data(), sizeof(double));
-    if (!std::isfinite(value)) {
-      return false;
-    }
+size_t DataSynonymAndIdEquationFacts::OperationHash::operator()(
+    const Operation& operation) const {
+  std::u32string hash;
+  hash.push_back(operation.opcode);
+  for (auto operand : operation.operands) {
+    hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand)));
   }
-  return true;
+  return std::hash<std::u32string>()(hash);
 }
 
-bool FactManager::ConstantUniformFacts::AddFact(
-    const protobufs::FactConstantUniform& fact, opt::IRContext* context) {
-  // Try to find a unique instruction that declares a variable such that the
-  // variable is decorated with the descriptor set and binding associated with
-  // the constant uniform fact.
-  opt::Instruction* uniform_variable = FindUniformVariable(
-      fact.uniform_buffer_element_descriptor(), context, true);
-
-  if (!uniform_variable) {
-    return false;
-  }
-
-  assert(SpvOpVariable == uniform_variable->opcode());
-  assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
-
-  auto should_be_uniform_pointer_type =
-      context->get_type_mgr()->GetType(uniform_variable->type_id());
-  if (!should_be_uniform_pointer_type->AsPointer()) {
-    return false;
-  }
-  if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
-      SpvStorageClassUniform) {
-    return false;
-  }
-  auto should_be_uniform_pointer_instruction =
-      context->get_def_use_mgr()->GetDef(uniform_variable->type_id());
-  auto composite_type =
-      should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
-
-  auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices(
-      context, composite_type,
-      fact.uniform_buffer_element_descriptor().index());
-  if (!final_element_type_id) {
-    return false;
-  }
-  auto final_element_type =
-      context->get_type_mgr()->GetType(final_element_type_id);
-  assert(final_element_type &&
-         "There should be a type corresponding to this id.");
-
-  if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
+bool DataSynonymAndIdEquationFacts::OperationEquals::operator()(
+    const Operation& first, const Operation& second) const {
+  // Equal operations require...
+  //
+  // Equal opcodes.
+  if (first.opcode != second.opcode) {
     return false;
   }
-  auto width = final_element_type->AsFloat()
-                   ? final_element_type->AsFloat()->width()
-                   : final_element_type->AsInteger()->width();
-
-  if (final_element_type->AsFloat() &&
-      !FloatingPointValueIsSuitable(fact, width)) {
+  // Matching operand counts.
+  if (first.operands.size() != second.operands.size()) {
     return false;
   }
-
-  auto required_words = (width + 32 - 1) / 32;
-  if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
-    return false;
-  }
-  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 and id equation facts
-
-// This helper struct represents the right hand side of an equation as an
-// operator applied to a number of data descriptor operands.
-struct Operation {
-  SpvOp opcode;
-  std::vector<const protobufs::DataDescriptor*> operands;
-};
-
-// Hashing for operations, to allow deterministic unordered sets.
-struct OperationHash {
-  size_t operator()(const Operation& operation) const {
-    std::u32string hash;
-    hash.push_back(operation.opcode);
-    for (auto operand : operation.operands) {
-      hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand)));
-    }
-    return std::hash<std::u32string>()(hash);
-  }
-};
-
-// Equality for operations, to allow deterministic unordered sets.
-struct OperationEquals {
-  bool operator()(const Operation& first, const Operation& second) const {
-    // Equal operations require...
-    //
-    // Equal opcodes.
-    if (first.opcode != second.opcode) {
-      return false;
-    }
-    // Matching operand counds.
-    if (first.operands.size() != second.operands.size()) {
+  // Equal operands.
+  for (uint32_t i = 0; i < first.operands.size(); i++) {
+    if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) {
       return false;
     }
-    // Equal operands.
-    for (uint32_t i = 0; i < first.operands.size(); i++) {
-      if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-};
-
-// A helper, for debugging, to represent an operation as a string.
-std::string ToString(const Operation& operation) {
-  std::stringstream stream;
-  stream << operation.opcode;
-  for (auto operand : operation.operands) {
-    stream << " " << *operand;
   }
-  return stream.str();
+  return true;
 }
 
-// The purpose of this class is to group the fields and data used to represent
-// facts about data synonyms and id equations.
-class FactManager::DataSynonymAndIdEquationFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context);
-
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context);
-
-  // See method in FactManager which delegates to this method.
-  std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
-      const protobufs::DataDescriptor& data_descriptor) const;
-
-  // See method in FactManager which delegates to this method.
-  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
-
-  // See method in FactManager which delegates to this method.
-  bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
-                    const protobufs::DataDescriptor& data_descriptor2) const;
-
-  // See method in FactManager which delegates to this method.
-  void ComputeClosureOfFacts(opt::IRContext* context,
-                             uint32_t maximum_equivalence_class_size);
-
- private:
-  using OperationSet =
-      std::unordered_set<Operation, OperationHash, OperationEquals>;
-
-  // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses
-  // into sub-components of the data descriptors, if they are composites, to
-  // record that their components are pairwise-synonymous.
-  void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1,
-                                   const protobufs::DataDescriptor& dd2,
-                                   opt::IRContext* context);
-
-  // Computes various corollary facts from the data descriptor |dd| if members
-  // of its equivalence class participate in equation facts with OpConvert*
-  // opcodes. The descriptor should be registered in the equivalence relation.
-  void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd,
-                                         opt::IRContext* context);
-
-  // Recurses into sub-components of the data descriptors, if they are
-  // composites, to record that their components are pairwise-synonymous.
-  void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
-                                        const protobufs::DataDescriptor& dd2,
-                                        opt::IRContext* context);
-
-  // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets
-  // of equations that are known about them.
-  void MakeEquivalent(const protobufs::DataDescriptor& dd1,
-                      const protobufs::DataDescriptor& dd2);
-
-  // Registers a data descriptor in the equivalence relation if it hasn't been
-  // registered yet, and returns its representative.
-  const protobufs::DataDescriptor* RegisterDataDescriptor(
-      const protobufs::DataDescriptor& dd);
-
-  // Returns true if and only if |dd1| and |dd2| are valid data descriptors
-  // whose associated data have compatible types. Two types are compatible if:
-  // - they are the same
-  // - they both are numerical or vectors of numerical components with the same
-  //   number of components and the same bit count per component
-  static bool DataDescriptorsAreWellFormedAndComparable(
-      opt::IRContext* context, const protobufs::DataDescriptor& dd1,
-      const protobufs::DataDescriptor& dd2);
-
-  OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const;
-
-  // Requires that |lhs_dd| and every element of |rhs_dds| is present in the
-  // |synonymous_| equivalence relation, but is not necessarily its own
-  // representative.  Records the fact that the equation
-  // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any
-  // corollaries, in the form of data synonym or equation facts, that follow
-  // from this and other known facts.
-  void AddEquationFactRecursive(
-      const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
-      const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
-      opt::IRContext* context);
-
-  // The data descriptors that are known to be synonymous with one another are
-  // captured by this equivalence relation.
-  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, this is
-  // an expensive operation, so it should be performed sparingly and only there
-  // is some chance of new facts being deduced.  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.
-  bool closure_computation_required_ = false;
-
-  // Represents a set of equations on data descriptors as a map indexed by
-  // left-hand-side, mapping a left-hand-side to a set of operations, each of
-  // which (together with the left-hand-side) defines an equation.
-  //
-  // All data descriptors occurring in equations are required to be present in
-  // the |synonymous_| equivalence relation, and to be their own representatives
-  // in that relation.
-  std::unordered_map<const protobufs::DataDescriptor*, OperationSet>
-      id_equations_;
-};
-
-void FactManager::DataSynonymAndIdEquationFacts::AddFact(
+void DataSynonymAndIdEquationFacts::AddFact(
     const protobufs::FactDataSynonym& fact, opt::IRContext* context) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
+  //  Assert that ids are not irrelevant.
+
   // Add the fact, including all facts relating sub-components of the data
   // descriptors that are involved.
   AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context);
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::AddFact(
+void DataSynonymAndIdEquationFacts::AddFact(
     const protobufs::FactIdEquation& fact, opt::IRContext* context) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
+  //  Assert that ids are not irrelevant.
+
   protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {});
 
   // Register the LHS in the equivalence relation if needed.
@@ -542,21 +73,23 @@ void FactManager::DataSynonymAndIdEquationFacts::AddFact(
 
   // Get equivalence class representatives for all ids used on the RHS of the
   // equation.
-  std::vector<const protobufs::DataDescriptor*> rhs_dd_ptrs;
+  std::vector<const protobufs::DataDescriptor*> rhs_dds;
   for (auto rhs_id : fact.rhs_id()) {
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
+    //  Assert that ids are not irrelevant.
+
     // Register a data descriptor based on this id in the equivalence relation
     // if needed, and then record the equivalence class representative.
-    rhs_dd_ptrs.push_back(
-        RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {})));
+    rhs_dds.push_back(RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {})));
   }
 
   // Now add the fact.
-  AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()),
-                           rhs_dd_ptrs, context);
+  AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()), rhs_dds,
+                           context);
 }
 
-FactManager::DataSynonymAndIdEquationFacts::OperationSet
-FactManager::DataSynonymAndIdEquationFacts::GetEquations(
+DataSynonymAndIdEquationFacts::OperationSet
+DataSynonymAndIdEquationFacts::GetEquations(
     const protobufs::DataDescriptor* lhs) const {
   auto existing = id_equations_.find(lhs);
   if (existing == id_equations_.end()) {
@@ -565,7 +98,7 @@ FactManager::DataSynonymAndIdEquationFacts::GetEquations(
   return existing->second;
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
+void DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
     const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
     const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
     opt::IRContext* context) {
@@ -717,7 +250,7 @@ void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
   }
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
+void DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
     const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2,
     opt::IRContext* context) {
   assert(DataDescriptorsAreWellFormedAndComparable(context, dd1, dd2));
@@ -736,9 +269,8 @@ void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
   ComputeCompositeDataSynonymFacts(dd1, dd2, context);
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::
-    ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd,
-                                      opt::IRContext* context) {
+void DataSynonymAndIdEquationFacts::ComputeConversionDataSynonymFacts(
+    const protobufs::DataDescriptor& dd, opt::IRContext* context) {
   assert(synonymous_.Exists(dd) &&
          "|dd| should've been registered in the equivalence relation");
 
@@ -789,10 +321,9 @@ void FactManager::DataSynonymAndIdEquationFacts::
   }
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::
-    ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
-                                     const protobufs::DataDescriptor& dd2,
-                                     opt::IRContext* context) {
+void DataSynonymAndIdEquationFacts::ComputeCompositeDataSynonymFacts(
+    const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2,
+    opt::IRContext* context) {
   // Check whether this is a synonym about composite objects. If it is,
   // we can recursively add synonym facts about their associated sub-components.
 
@@ -862,7 +393,7 @@ void FactManager::DataSynonymAndIdEquationFacts::
   }
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
+void DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
     opt::IRContext* context, uint32_t maximum_equivalence_class_size) {
   // 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
@@ -1144,7 +675,7 @@ void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
   }
 }
 
-void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent(
+void DataSynonymAndIdEquationFacts::MakeEquivalent(
     const protobufs::DataDescriptor& dd1,
     const protobufs::DataDescriptor& dd2) {
   // Register the data descriptors if they are not already known to the
@@ -1218,16 +749,15 @@ void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent(
 }
 
 const protobufs::DataDescriptor*
-FactManager::DataSynonymAndIdEquationFacts::RegisterDataDescriptor(
+DataSynonymAndIdEquationFacts::RegisterDataDescriptor(
     const protobufs::DataDescriptor& dd) {
   return synonymous_.Exists(dd) ? synonymous_.Find(&dd)
                                 : synonymous_.Register(dd);
 }
 
-bool FactManager::DataSynonymAndIdEquationFacts::
-    DataDescriptorsAreWellFormedAndComparable(
-        opt::IRContext* context, const protobufs::DataDescriptor& dd1,
-        const protobufs::DataDescriptor& dd2) {
+bool DataSynonymAndIdEquationFacts::DataDescriptorsAreWellFormedAndComparable(
+    opt::IRContext* context, const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) {
   auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
       context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
       dd1.index());
@@ -1238,6 +768,13 @@ bool FactManager::DataSynonymAndIdEquationFacts::
   if (end_type_id_1 == 0 || end_type_id_2 == 0) {
     return false;
   }
+  // Neither end type is allowed to be void.
+  if (context->get_def_use_mgr()->GetDef(end_type_id_1)->opcode() ==
+          SpvOpTypeVoid ||
+      context->get_def_use_mgr()->GetDef(end_type_id_2)->opcode() ==
+          SpvOpTypeVoid) {
+    return false;
+  }
   // If the end types are the same, the data descriptors are comparable.
   if (end_type_id_1 == end_type_id_2) {
     return true;
@@ -1292,17 +829,16 @@ bool FactManager::DataSynonymAndIdEquationFacts::
 }
 
 std::vector<const protobufs::DataDescriptor*>
-FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor(
+DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor(
     const protobufs::DataDescriptor& data_descriptor) const {
   if (synonymous_.Exists(data_descriptor)) {
     return synonymous_.GetEquivalenceClass(data_descriptor);
   }
-  return std::vector<const protobufs::DataDescriptor*>();
+  return {};
 }
 
 std::vector<uint32_t>
-FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown()
-    const {
+DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() const {
   std::vector<uint32_t> result;
   for (auto& data_descriptor : synonymous_.GetAllKnownValues()) {
     if (data_descriptor->index().empty()) {
@@ -1312,7 +848,7 @@ FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown()
   return result;
 }
 
-bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous(
+bool DataSynonymAndIdEquationFacts::IsSynonymous(
     const protobufs::DataDescriptor& data_descriptor1,
     const protobufs::DataDescriptor& data_descriptor2) const {
   return synonymous_.Exists(data_descriptor1) &&
@@ -1320,284 +856,6 @@ bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous(
          synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
 }
 
-// End of data synonym facts
-//==============================
-
-//==============================
-// Dead block facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about data blocks.
-class FactManager::DeadBlockFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactBlockIsDead& fact);
-
-  // See method in FactManager which delegates to this method.
-  bool BlockIsDead(uint32_t block_id) const;
-
- private:
-  std::set<uint32_t> dead_block_ids_;
-};
-
-void FactManager::DeadBlockFacts::AddFact(
-    const protobufs::FactBlockIsDead& fact) {
-  dead_block_ids_.insert(fact.block_id());
-}
-
-bool FactManager::DeadBlockFacts::BlockIsDead(uint32_t block_id) const {
-  return dead_block_ids_.count(block_id) != 0;
-}
-
-// End of dead block facts
-//==============================
-
-//==============================
-// Livesafe function facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about livesafe functions.
-class FactManager::LivesafeFunctionFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactFunctionIsLivesafe& fact);
-
-  // See method in FactManager which delegates to this method.
-  bool FunctionIsLivesafe(uint32_t function_id) const;
-
- private:
-  std::set<uint32_t> livesafe_function_ids_;
-};
-
-void FactManager::LivesafeFunctionFacts::AddFact(
-    const protobufs::FactFunctionIsLivesafe& fact) {
-  livesafe_function_ids_.insert(fact.function_id());
-}
-
-bool FactManager::LivesafeFunctionFacts::FunctionIsLivesafe(
-    uint32_t function_id) const {
-  return livesafe_function_ids_.count(function_id) != 0;
-}
-
-// End of livesafe function facts
-//==============================
-
-//==============================
-// Irrelevant value facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about various irrelevant values in the module.
-class FactManager::IrrelevantValueFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact);
-
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactIdIsIrrelevant& fact);
-
-  // See method in FactManager which delegates to this method.
-  bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
-
-  // See method in FactManager which delegates to this method.
-  bool IdIsIrrelevant(uint32_t pointer_id) const;
-
- private:
-  std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_;
-  std::unordered_set<uint32_t> irrelevant_ids_;
-};
-
-void FactManager::IrrelevantValueFacts::AddFact(
-    const protobufs::FactPointeeValueIsIrrelevant& fact) {
-  pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id());
-}
-
-void FactManager::IrrelevantValueFacts::AddFact(
-    const protobufs::FactIdIsIrrelevant& fact) {
-  irrelevant_ids_.insert(fact.result_id());
-}
-
-bool FactManager::IrrelevantValueFacts::PointeeValueIsIrrelevant(
-    uint32_t pointer_id) const {
-  return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0;
-}
-
-bool FactManager::IrrelevantValueFacts::IdIsIrrelevant(
-    uint32_t pointer_id) const {
-  return irrelevant_ids_.count(pointer_id) != 0;
-}
-
-// End of arbitrarily-valued variable facts
-//==============================
-
-FactManager::FactManager()
-    : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()),
-      data_synonym_and_id_equation_facts_(
-          MakeUnique<DataSynonymAndIdEquationFacts>()),
-      dead_block_facts_(MakeUnique<DeadBlockFacts>()),
-      livesafe_function_facts_(MakeUnique<LivesafeFunctionFacts>()),
-      irrelevant_value_facts_(MakeUnique<IrrelevantValueFacts>()) {}
-
-FactManager::~FactManager() = default;
-
-void FactManager::AddFacts(const MessageConsumer& message_consumer,
-                           const protobufs::FactSequence& initial_facts,
-                           opt::IRContext* context) {
-  for (auto& fact : initial_facts.fact()) {
-    if (!AddFact(fact, context)) {
-      message_consumer(
-          SPV_MSG_WARNING, nullptr, {},
-          ("Invalid fact " + ToString(fact) + " ignored.").c_str());
-    }
-  }
-}
-
-bool FactManager::AddFact(const fuzz::protobufs::Fact& fact,
-                          opt::IRContext* context) {
-  switch (fact.fact_case()) {
-    case protobufs::Fact::kConstantUniformFact:
-      return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
-                                              context);
-    case protobufs::Fact::kDataSynonymFact:
-      data_synonym_and_id_equation_facts_->AddFact(fact.data_synonym_fact(),
-                                                   context);
-      return true;
-    case protobufs::Fact::kBlockIsDeadFact:
-      dead_block_facts_->AddFact(fact.block_is_dead_fact());
-      return true;
-    case protobufs::Fact::kFunctionIsLivesafeFact:
-      livesafe_function_facts_->AddFact(fact.function_is_livesafe_fact());
-      return true;
-    default:
-      assert(false && "Unknown fact type.");
-      return false;
-  }
-}
-
-void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1,
-                                     const protobufs::DataDescriptor& data2,
-                                     opt::IRContext* context) {
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550):
-  //  assert that neither |data1| nor |data2| are irrelevant.
-  protobufs::FactDataSynonym fact;
-  *fact.mutable_data1() = data1;
-  *fact.mutable_data2() = data2;
-  data_synonym_and_id_equation_facts_->AddFact(fact, context);
-}
-
-std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
-    opt::IRContext* ir_context, uint32_t type_id) const {
-  return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType(
-      ir_context, type_id);
-}
-
-const std::vector<protobufs::UniformBufferElementDescriptor>
-FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
-                                              uint32_t constant_id) const {
-  return uniform_constant_facts_->GetUniformDescriptorsForConstant(ir_context,
-                                                                   constant_id);
-}
-
-uint32_t FactManager::GetConstantFromUniformDescriptor(
-    opt::IRContext* context,
-    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
-  return uniform_constant_facts_->GetConstantFromUniformDescriptor(
-      context, uniform_descriptor);
-}
-
-std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
-    const {
-  return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown();
-}
-
-const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
-FactManager::GetConstantUniformFactsAndTypes() const {
-  return uniform_constant_facts_->GetConstantUniformFactsAndTypes();
-}
-
-std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
-  return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown();
-}
-
-std::vector<const protobufs::DataDescriptor*>
-FactManager::GetSynonymsForDataDescriptor(
-    const protobufs::DataDescriptor& data_descriptor) const {
-  return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor(
-      data_descriptor);
-}
-
-std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
-    uint32_t id) const {
-  return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {}));
-}
-
-bool FactManager::IsSynonymous(
-    const protobufs::DataDescriptor& data_descriptor1,
-    const protobufs::DataDescriptor& data_descriptor2) const {
-  return data_synonym_and_id_equation_facts_->IsSynonymous(data_descriptor1,
-                                                           data_descriptor2);
-}
-
-bool FactManager::BlockIsDead(uint32_t block_id) const {
-  return dead_block_facts_->BlockIsDead(block_id);
-}
-
-void FactManager::AddFactBlockIsDead(uint32_t block_id) {
-  protobufs::FactBlockIsDead fact;
-  fact.set_block_id(block_id);
-  dead_block_facts_->AddFact(fact);
-}
-
-bool FactManager::FunctionIsLivesafe(uint32_t function_id) const {
-  return livesafe_function_facts_->FunctionIsLivesafe(function_id);
-}
-
-void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) {
-  protobufs::FactFunctionIsLivesafe fact;
-  fact.set_function_id(function_id);
-  livesafe_function_facts_->AddFact(fact);
-}
-
-bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
-  return irrelevant_value_facts_->PointeeValueIsIrrelevant(pointer_id);
-}
-
-bool FactManager::IdIsIrrelevant(uint32_t result_id) const {
-  return irrelevant_value_facts_->IdIsIrrelevant(result_id);
-}
-
-void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
-  protobufs::FactPointeeValueIsIrrelevant fact;
-  fact.set_pointer_id(pointer_id);
-  irrelevant_value_facts_->AddFact(fact);
-}
-
-void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) {
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550):
-  //  assert that |result_id| is not a part of any DataSynonym fact.
-  protobufs::FactIdIsIrrelevant fact;
-  fact.set_result_id(result_id);
-  irrelevant_value_facts_->AddFact(fact);
-}
-
-void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
-                                    const std::vector<uint32_t>& rhs_id,
-                                    opt::IRContext* context) {
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550):
-  //  assert that elements of |rhs_id| and |lhs_id| are not irrelevant.
-  protobufs::FactIdEquation fact;
-  fact.set_lhs_id(lhs_id);
-  fact.set_opcode(opcode);
-  for (auto an_rhs_id : rhs_id) {
-    fact.add_rhs_id(an_rhs_id);
-  }
-  data_synonym_and_id_equation_facts_->AddFact(fact, context);
-}
-
-void FactManager::ComputeClosureOfFacts(
-    opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) {
-  data_synonym_and_id_equation_facts_->ComputeClosureOfFacts(
-      ir_context, maximum_equivalence_class_size);
-}
-
+}  // namespace fact_manager
 }  // namespace fuzz
 }  // namespace spvtools

+ 156 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h

@@ -0,0 +1,156 @@
+// 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_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
+
+#include <unordered_set>
+#include <vector>
+
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/equivalence_relation.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about data synonyms and id equations.
+class DataSynonymAndIdEquationFacts {
+ public:
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context);
+
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context);
+
+  // See method in FactManager which delegates to this method.
+  std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
+      const protobufs::DataDescriptor& data_descriptor) const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
+
+  // See method in FactManager which delegates to this method.
+  bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
+                    const protobufs::DataDescriptor& data_descriptor2) const;
+
+  // See method in FactManager which delegates to this method.
+  void ComputeClosureOfFacts(opt::IRContext* context,
+                             uint32_t maximum_equivalence_class_size);
+
+ private:
+  // This helper struct represents the right hand side of an equation as an
+  // operator applied to a number of data descriptor operands.
+  struct Operation {
+    SpvOp opcode;
+    std::vector<const protobufs::DataDescriptor*> operands;
+  };
+
+  // Hashing for operations, to allow deterministic unordered sets.
+  struct OperationHash {
+    size_t operator()(const Operation& operation) const;
+  };
+
+  // Equality for operations, to allow deterministic unordered sets.
+  struct OperationEquals {
+    bool operator()(const Operation& first, const Operation& second) const;
+  };
+
+  using OperationSet =
+      std::unordered_set<Operation, OperationHash, OperationEquals>;
+
+  // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses
+  // into sub-components of the data descriptors, if they are composites, to
+  // record that their components are pairwise-synonymous.
+  void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1,
+                                   const protobufs::DataDescriptor& dd2,
+                                   opt::IRContext* context);
+
+  // Computes various corollary facts from the data descriptor |dd| if members
+  // of its equivalence class participate in equation facts with OpConvert*
+  // opcodes. The descriptor should be registered in the equivalence relation.
+  void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd,
+                                         opt::IRContext* context);
+
+  // Recurses into sub-components of the data descriptors, if they are
+  // composites, to record that their components are pairwise-synonymous.
+  void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
+                                        const protobufs::DataDescriptor& dd2,
+                                        opt::IRContext* context);
+
+  // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets
+  // of equations that are known about them.
+  void MakeEquivalent(const protobufs::DataDescriptor& dd1,
+                      const protobufs::DataDescriptor& dd2);
+
+  // Registers a data descriptor in the equivalence relation if it hasn't been
+  // registered yet, and returns its representative.
+  const protobufs::DataDescriptor* RegisterDataDescriptor(
+      const protobufs::DataDescriptor& dd);
+
+  // Returns true if and only if |dd1| and |dd2| are valid data descriptors
+  // whose associated data have compatible types. Two types are compatible if:
+  // - they are the same
+  // - they both are numerical or vectors of numerical components with the same
+  //   number of components and the same bit count per component
+  static bool DataDescriptorsAreWellFormedAndComparable(
+      opt::IRContext* context, const protobufs::DataDescriptor& dd1,
+      const protobufs::DataDescriptor& dd2);
+
+  OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const;
+
+  // Requires that |lhs_dd| and every element of |rhs_dds| is present in the
+  // |synonymous_| equivalence relation, but is not necessarily its own
+  // representative.  Records the fact that the equation
+  // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any
+  // corollaries, in the form of data synonym or equation facts, that follow
+  // from this and other known facts.
+  void AddEquationFactRecursive(
+      const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
+      const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
+      opt::IRContext* context);
+
+  // The data descriptors that are known to be synonymous with one another are
+  // captured by this equivalence relation.
+  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, this is
+  // an expensive operation, so it should be performed sparingly and only there
+  // is some chance of new facts being deduced.  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.
+  bool closure_computation_required_ = false;
+
+  // Represents a set of equations on data descriptors as a map indexed by
+  // left-hand-side, mapping a left-hand-side to a set of operations, each of
+  // which (together with the left-hand-side) defines an equation.
+  //
+  // All data descriptors occurring in equations are required to be present in
+  // the |synonymous_| equivalence relation, and to be their own representatives
+  // in that relation.
+  std::unordered_map<const protobufs::DataDescriptor*, OperationSet>
+      id_equations_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_

+ 31 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.cpp

@@ -0,0 +1,31 @@
+// 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/fact_manager/dead_block_facts.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+void DeadBlockFacts::AddFact(const protobufs::FactBlockIsDead& fact) {
+  dead_block_ids_.insert(fact.block_id());
+}
+
+bool DeadBlockFacts::BlockIsDead(uint32_t block_id) const {
+  return dead_block_ids_.count(block_id) != 0;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools

+ 44 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/dead_block_facts.h

@@ -0,0 +1,44 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
+
+#include <unordered_set>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about data blocks.
+class DeadBlockFacts {
+ public:
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactBlockIsDead& fact);
+
+  // See method in FactManager which delegates to this method.
+  bool BlockIsDead(uint32_t block_id) const;
+
+ private:
+  std::unordered_set<uint32_t> dead_block_ids_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_

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

@@ -0,0 +1,244 @@
+// 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 "fact_manager.h"
+
+#include <sstream>
+#include <unordered_map>
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+std::string ToString(const protobufs::FactConstantUniform& fact) {
+  std::stringstream stream;
+  stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set()
+         << ", " << fact.uniform_buffer_element_descriptor().binding() << ")[";
+
+  bool first = true;
+  for (auto index : fact.uniform_buffer_element_descriptor().index()) {
+    if (first) {
+      first = false;
+    } else {
+      stream << ", ";
+    }
+    stream << index;
+  }
+
+  stream << "] == [";
+
+  first = true;
+  for (auto constant_word : fact.constant_word()) {
+    if (first) {
+      first = false;
+    } else {
+      stream << ", ";
+    }
+    stream << constant_word;
+  }
+
+  stream << "]";
+  return stream.str();
+}
+
+std::string ToString(const protobufs::FactDataSynonym& fact) {
+  std::stringstream stream;
+  stream << fact.data1() << " = " << fact.data2();
+  return stream.str();
+}
+
+std::string ToString(const protobufs::FactIdEquation& fact) {
+  std::stringstream stream;
+  stream << fact.lhs_id();
+  stream << " " << static_cast<SpvOp>(fact.opcode());
+  for (auto rhs_id : fact.rhs_id()) {
+    stream << " " << rhs_id;
+  }
+  return stream.str();
+}
+
+std::string ToString(const protobufs::Fact& fact) {
+  switch (fact.fact_case()) {
+    case protobufs::Fact::kConstantUniformFact:
+      return ToString(fact.constant_uniform_fact());
+    case protobufs::Fact::kDataSynonymFact:
+      return ToString(fact.data_synonym_fact());
+    case protobufs::Fact::kIdEquationFact:
+      return ToString(fact.id_equation_fact());
+    default:
+      assert(false && "Stringification not supported for this fact.");
+      return "";
+  }
+}
+
+}  // namespace
+
+void FactManager::AddFacts(const MessageConsumer& message_consumer,
+                           const protobufs::FactSequence& initial_facts,
+                           opt::IRContext* context) {
+  for (auto& fact : initial_facts.fact()) {
+    if (!AddFact(fact, context)) {
+      auto message = "Invalid fact " + ToString(fact) + " ignored.";
+      message_consumer(SPV_MSG_WARNING, nullptr, {}, message.c_str());
+    }
+  }
+}
+
+bool FactManager::AddFact(const fuzz::protobufs::Fact& fact,
+                          opt::IRContext* context) {
+  switch (fact.fact_case()) {
+    case protobufs::Fact::kConstantUniformFact:
+      return constant_uniform_facts_.AddFact(fact.constant_uniform_fact(),
+                                             context);
+    case protobufs::Fact::kDataSynonymFact:
+      data_synonym_and_id_equation_facts_.AddFact(fact.data_synonym_fact(),
+                                                  context);
+      return true;
+    case protobufs::Fact::kBlockIsDeadFact:
+      dead_block_facts_.AddFact(fact.block_is_dead_fact());
+      return true;
+    case protobufs::Fact::kFunctionIsLivesafeFact:
+      livesafe_function_facts_.AddFact(fact.function_is_livesafe_fact());
+      return true;
+    default:
+      assert(false && "Unknown fact type.");
+      return false;
+  }
+}
+
+void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1,
+                                     const protobufs::DataDescriptor& data2,
+                                     opt::IRContext* context) {
+  protobufs::FactDataSynonym fact;
+  *fact.mutable_data1() = data1;
+  *fact.mutable_data2() = data2;
+  data_synonym_and_id_equation_facts_.AddFact(fact, context);
+}
+
+std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
+    opt::IRContext* ir_context, uint32_t type_id) const {
+  return constant_uniform_facts_.GetConstantsAvailableFromUniformsForType(
+      ir_context, type_id);
+}
+
+std::vector<protobufs::UniformBufferElementDescriptor>
+FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
+                                              uint32_t constant_id) const {
+  return constant_uniform_facts_.GetUniformDescriptorsForConstant(ir_context,
+                                                                  constant_id);
+}
+
+uint32_t FactManager::GetConstantFromUniformDescriptor(
+    opt::IRContext* context,
+    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+  return constant_uniform_facts_.GetConstantFromUniformDescriptor(
+      context, uniform_descriptor);
+}
+
+std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
+    const {
+  return constant_uniform_facts_.GetTypesForWhichUniformValuesAreKnown();
+}
+
+const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+FactManager::GetConstantUniformFactsAndTypes() const {
+  return constant_uniform_facts_.GetConstantUniformFactsAndTypes();
+}
+
+std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
+  return data_synonym_and_id_equation_facts_.GetIdsForWhichSynonymsAreKnown();
+}
+
+std::vector<const protobufs::DataDescriptor*>
+FactManager::GetSynonymsForDataDescriptor(
+    const protobufs::DataDescriptor& data_descriptor) const {
+  return data_synonym_and_id_equation_facts_.GetSynonymsForDataDescriptor(
+      data_descriptor);
+}
+
+std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
+    uint32_t id) const {
+  return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {}));
+}
+
+bool FactManager::IsSynonymous(
+    const protobufs::DataDescriptor& data_descriptor1,
+    const protobufs::DataDescriptor& data_descriptor2) const {
+  return data_synonym_and_id_equation_facts_.IsSynonymous(data_descriptor1,
+                                                          data_descriptor2);
+}
+
+bool FactManager::BlockIsDead(uint32_t block_id) const {
+  return dead_block_facts_.BlockIsDead(block_id);
+}
+
+void FactManager::AddFactBlockIsDead(uint32_t block_id) {
+  protobufs::FactBlockIsDead fact;
+  fact.set_block_id(block_id);
+  dead_block_facts_.AddFact(fact);
+}
+
+bool FactManager::FunctionIsLivesafe(uint32_t function_id) const {
+  return livesafe_function_facts_.FunctionIsLivesafe(function_id);
+}
+
+void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) {
+  protobufs::FactFunctionIsLivesafe fact;
+  fact.set_function_id(function_id);
+  livesafe_function_facts_.AddFact(fact);
+}
+
+bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
+  return irrelevant_value_facts_.PointeeValueIsIrrelevant(pointer_id);
+}
+
+bool FactManager::IdIsIrrelevant(uint32_t result_id) const {
+  return irrelevant_value_facts_.IdIsIrrelevant(result_id);
+}
+
+void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
+  protobufs::FactPointeeValueIsIrrelevant fact;
+  fact.set_pointer_id(pointer_id);
+  irrelevant_value_facts_.AddFact(fact);
+}
+
+void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) {
+  protobufs::FactIdIsIrrelevant fact;
+  fact.set_result_id(result_id);
+  irrelevant_value_facts_.AddFact(fact);
+}
+
+void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
+                                    const std::vector<uint32_t>& rhs_id,
+                                    opt::IRContext* context) {
+  protobufs::FactIdEquation fact;
+  fact.set_lhs_id(lhs_id);
+  fact.set_opcode(opcode);
+  for (auto an_rhs_id : rhs_id) {
+    fact.add_rhs_id(an_rhs_id);
+  }
+  data_synonym_and_id_equation_facts_.AddFact(fact, context);
+}
+
+void FactManager::ComputeClosureOfFacts(
+    opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) {
+  data_synonym_and_id_equation_facts_.ComputeClosureOfFacts(
+      ir_context, maximum_equivalence_class_size);
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 16 - 35
3rdparty/spirv-tools/source/fuzz/fact_manager.h → 3rdparty/spirv-tools/source/fuzz/fact_manager/fact_manager.h

@@ -12,15 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FACT_MANAGER_H_
-#define SOURCE_FUZZ_FACT_MANAGER_H_
+#ifndef SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
+#define SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
 
-#include <memory>
 #include <set>
 #include <utility>
 #include <vector>
 
 #include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fact_manager/constant_uniform_facts.h"
+#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
+#include "source/fuzz/fact_manager/dead_block_facts.h"
+#include "source/fuzz/fact_manager/irrelevant_value_facts.h"
+#include "source/fuzz/fact_manager/livesafe_function_facts.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/opt/constants.h"
 
@@ -38,10 +42,6 @@ namespace fuzz {
 // the module.
 class FactManager {
  public:
-  FactManager();
-
-  ~FactManager();
-
   // Adds all the facts from |facts|, checking them for validity with respect to
   // |context|.  Warnings about invalid facts are communicated via
   // |message_consumer|; such facts are otherwise ignored.
@@ -117,7 +117,7 @@ class FactManager {
 
   // Provides details of all uniform elements that are known to be equal to the
   // constant associated with |constant_id| in |ir_context|.
-  const std::vector<protobufs::UniformBufferElementDescriptor>
+  std::vector<protobufs::UniformBufferElementDescriptor>
   GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
                                    uint32_t constant_id) const;
 
@@ -198,35 +198,16 @@ class FactManager {
   //==============================
 
  private:
-  // For each distinct kind of fact to be managed, we use a separate opaque
-  // class type.
-
-  class ConstantUniformFacts;  // Opaque class for management of
-                               // constant uniform facts.
-  std::unique_ptr<ConstantUniformFacts>
-      uniform_constant_facts_;  // Unique pointer to internal data.
-
-  class DataSynonymAndIdEquationFacts;  // Opaque class for management of data
-                                        // synonym and id equation facts.
-  std::unique_ptr<DataSynonymAndIdEquationFacts>
-      data_synonym_and_id_equation_facts_;  // Unique pointer to internal data.
-
-  class DeadBlockFacts;  // Opaque class for management of dead block facts.
-  std::unique_ptr<DeadBlockFacts>
-      dead_block_facts_;  // Unique pointer to internal data.
-
-  class LivesafeFunctionFacts;  // Opaque class for management of livesafe
-                                // function facts.
-  std::unique_ptr<LivesafeFunctionFacts>
-      livesafe_function_facts_;  // Unique pointer to internal data.
-
-  class IrrelevantValueFacts;  // Opaque class for management of
-  // facts about various irrelevant values in the module.
-  std::unique_ptr<IrrelevantValueFacts>
-      irrelevant_value_facts_;  // Unique pointer to internal data.
+  // Keep these in alphabetical order.
+  fact_manager::ConstantUniformFacts constant_uniform_facts_;
+  fact_manager::DataSynonymAndIdEquationFacts
+      data_synonym_and_id_equation_facts_;
+  fact_manager::DeadBlockFacts dead_block_facts_;
+  fact_manager::LivesafeFunctionFacts livesafe_function_facts_;
+  fact_manager::IrrelevantValueFacts irrelevant_value_facts_;
 };
 
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FACT_MANAGER_H_
+#endif  // SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_

+ 51 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.cpp

@@ -0,0 +1,51 @@
+// 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/fact_manager/irrelevant_value_facts.h"
+
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+void IrrelevantValueFacts::AddFact(
+    const protobufs::FactPointeeValueIsIrrelevant& fact) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
+  //  Assert that the id does not participate in DataSynonym facts and is a
+  //  pointer.
+
+  pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id());
+}
+
+void IrrelevantValueFacts::AddFact(const protobufs::FactIdIsIrrelevant& fact) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550)
+  //  Assert that the id does not participate in DataSynonym facts and is not a
+  //  pointer.
+
+  irrelevant_ids_.insert(fact.result_id());
+}
+
+bool IrrelevantValueFacts::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
+  return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0;
+}
+
+bool IrrelevantValueFacts::IdIsIrrelevant(uint32_t pointer_id) const {
+  return irrelevant_ids_.count(pointer_id) != 0;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools

+ 52 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/irrelevant_value_facts.h

@@ -0,0 +1,52 @@
+// 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_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
+
+#include <unordered_set>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about various irrelevant values in the module.
+class IrrelevantValueFacts {
+ public:
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact);
+
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactIdIsIrrelevant& fact);
+
+  // See method in FactManager which delegates to this method.
+  bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
+
+  // See method in FactManager which delegates to this method.
+  bool IdIsIrrelevant(uint32_t pointer_id) const;
+
+ private:
+  std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_;
+  std::unordered_set<uint32_t> irrelevant_ids_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_

+ 32 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.cpp

@@ -0,0 +1,32 @@
+// 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/fact_manager/livesafe_function_facts.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+void LivesafeFunctionFacts::AddFact(
+    const protobufs::FactFunctionIsLivesafe& fact) {
+  livesafe_function_ids_.insert(fact.function_id());
+}
+
+bool LivesafeFunctionFacts::FunctionIsLivesafe(uint32_t function_id) const {
+  return livesafe_function_ids_.count(function_id) != 0;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools

+ 44 - 0
3rdparty/spirv-tools/source/fuzz/fact_manager/livesafe_function_facts.h

@@ -0,0 +1,44 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
+
+#include <unordered_set>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about livesafe functions.
+class LivesafeFunctionFacts {
+ public:
+  // See method in FactManager which delegates to this method.
+  void AddFact(const protobufs::FactFunctionIsLivesafe& fact);
+
+  // See method in FactManager which delegates to this method.
+  bool FunctionIsLivesafe(uint32_t function_id) const;
+
+ private:
+  std::unordered_set<uint32_t> livesafe_function_ids_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_

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

@@ -14,7 +14,7 @@
 
 #include "source/fuzz/force_render_red.h"
 
-#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation_context.h"
@@ -26,9 +26,6 @@
 #include "source/util/make_unique.h"
 #include "tools/util/cli_consumer.h"
 
-#include <algorithm>
-#include <utility>
-
 namespace spvtools {
 namespace fuzz {
 

+ 36 - 49
3rdparty/spirv-tools/source/fuzz/fuzzer.cpp

@@ -16,9 +16,8 @@
 
 #include <cassert>
 #include <memory>
-#include <sstream>
 
-#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass_add_access_chains.h"
 #include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
@@ -35,6 +34,7 @@
 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
+#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_parameters.h"
 #include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
 #include "source/fuzz/fuzzer_pass_add_stores.h"
@@ -49,6 +49,7 @@
 #include "source/fuzz/fuzzer_pass_construct_composites.h"
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/fuzzer_pass_inline_functions.h"
 #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
 #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
@@ -110,51 +111,32 @@ void MaybeAddPass(
 
 }  // namespace
 
-struct Fuzzer::Impl {
-  Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass,
-       spv_validator_options options)
-      : target_env(env),
-        seed(random_seed),
-        validate_after_each_fuzzer_pass(validate_after_each_pass),
-        validator_options(options) {}
-
-  bool ApplyPassAndCheckValidity(FuzzerPass* pass,
-                                 const opt::IRContext& ir_context,
-                                 const spvtools::SpirvTools& tools) const;
-
-  const spv_target_env target_env;       // Target environment.
-  MessageConsumer consumer;              // Message consumer.
-  const uint32_t seed;                   // Seed for random number generator.
-  bool validate_after_each_fuzzer_pass;  // Determines whether the validator
-                                         // should be invoked after every fuzzer
-                                         // pass.
-  spv_validator_options validator_options;  // Options to control validation.
-};
-
-Fuzzer::Fuzzer(spv_target_env env, uint32_t seed,
+Fuzzer::Fuzzer(spv_target_env target_env, uint32_t seed,
                bool validate_after_each_fuzzer_pass,
                spv_validator_options validator_options)
-    : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
-                             validator_options)) {}
+    : target_env_(target_env),
+      seed_(seed),
+      validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass),
+      validator_options_(validator_options) {}
 
 Fuzzer::~Fuzzer() = default;
 
-void Fuzzer::SetMessageConsumer(MessageConsumer c) {
-  impl_->consumer = std::move(c);
+void Fuzzer::SetMessageConsumer(MessageConsumer consumer) {
+  consumer_ = std::move(consumer);
 }
 
-bool Fuzzer::Impl::ApplyPassAndCheckValidity(
+bool Fuzzer::ApplyPassAndCheckValidity(
     FuzzerPass* pass, const opt::IRContext& ir_context,
     const spvtools::SpirvTools& tools) const {
   pass->Apply();
-  if (validate_after_each_fuzzer_pass) {
+  if (validate_after_each_fuzzer_pass_) {
     std::vector<uint32_t> binary_to_validate;
     ir_context.module()->ToBinary(&binary_to_validate, false);
     if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
-                        validator_options)) {
-      consumer(SPV_MSG_INFO, nullptr, {},
-               "Binary became invalid during fuzzing (set a breakpoint to "
-               "inspect); stopping.");
+                        validator_options_)) {
+      consumer_(SPV_MSG_INFO, nullptr, {},
+                "Binary became invalid during fuzzing (set a breakpoint to "
+                "inspect); stopping.");
       return false;
     }
   }
@@ -171,29 +153,28 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
   // header files being used.
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-  spvtools::SpirvTools tools(impl_->target_env);
-  tools.SetMessageConsumer(impl_->consumer);
+  spvtools::SpirvTools tools(target_env_);
+  tools.SetMessageConsumer(consumer_);
   if (!tools.IsValid()) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Failed to create SPIRV-Tools interface; stopping.");
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "Failed to create SPIRV-Tools interface; stopping.");
     return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface;
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size(),
-                      impl_->validator_options)) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Initial binary is invalid; stopping.");
+  if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "Initial binary is invalid; stopping.");
     return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
   }
 
   // Build the module from the input binary.
-  std::unique_ptr<opt::IRContext> ir_context = BuildModule(
-      impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+  std::unique_ptr<opt::IRContext> ir_context =
+      BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size());
   assert(ir_context);
 
   // Make a PRNG from the seed passed to the fuzzer on creation.
-  PseudoRandomGenerator random_generator(impl_->seed);
+  PseudoRandomGenerator random_generator(seed_);
 
   // The fuzzer will introduce new ids into the module.  The module's id bound
   // gives the smallest id that can be used for this purpose.  We add an offset
@@ -206,9 +187,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
   FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id);
 
   FactManager fact_manager;
-  fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
+  fact_manager.AddFacts(consumer_, initial_facts, ir_context.get());
   TransformationContext transformation_context(&fact_manager,
-                                               impl_->validator_options);
+                                               validator_options_);
 
   // Apply some semantics-preserving passes.
   std::vector<std::unique_ptr<FuzzerPass>> passes;
@@ -255,6 +236,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
     MaybeAddPass<FuzzerPassAddLoopPreheaders>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddOpPhiSynonyms>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassAddParameters>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
@@ -282,6 +266,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
     MaybeAddPass<FuzzerPassDonateModules>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out, donor_suppliers);
+    MaybeAddPass<FuzzerPassInlineFunctions>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassInvertComparisonOperators>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
@@ -348,7 +335,7 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
          (is_first ||
           fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
     is_first = false;
-    if (!impl_->ApplyPassAndCheckValidity(
+    if (!ApplyPassAndCheckValidity(
             passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context,
             tools)) {
       return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
@@ -392,7 +379,7 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
       &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
   for (auto& pass : final_passes) {
-    if (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
+    if (!ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
       return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
     }
   }

+ 25 - 5
3rdparty/spirv-tools/source/fuzz/fuzzer.h

@@ -18,6 +18,7 @@
 #include <memory>
 #include <vector>
 
+#include "source/fuzz/fuzzer_pass.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "spirv-tools/libspirv.hpp"
@@ -37,11 +38,11 @@ class Fuzzer {
     kInitialBinaryInvalid,
   };
 
-  // Constructs a fuzzer from the given target environment |env|.  |seed| is a
-  // seed for pseudo-random number generation.
+  // Constructs a fuzzer from the given target environment |target_env|.  |seed|
+  // is a seed for pseudo-random number generation.
   // |validate_after_each_fuzzer_pass| controls whether the validator will be
   // invoked after every fuzzer pass is applied.
-  Fuzzer(spv_target_env env, uint32_t seed,
+  Fuzzer(spv_target_env target_env, uint32_t seed,
          bool validate_after_each_fuzzer_pass,
          spv_validator_options validator_options);
 
@@ -71,8 +72,27 @@ class Fuzzer {
       protobufs::TransformationSequence* transformation_sequence_out) const;
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  // Applies |pass|, which must be a pass constructed with |ir_context|, and
+  // then returns true if and only if |ir_context| is valid.  |tools| is used to
+  // check validity.
+  bool ApplyPassAndCheckValidity(FuzzerPass* pass,
+                                 const opt::IRContext& ir_context,
+                                 const spvtools::SpirvTools& tools) const;
+
+  // Target environment.
+  const spv_target_env target_env_;
+
+  // Message consumer.
+  MessageConsumer consumer_;
+
+  // Seed for random number generator.
+  const uint32_t seed_;
+
+  // Determines whether the validator should be invoked after every fuzzer pass.
+  bool validate_after_each_fuzzer_pass_;
+
+  // Options to control validation.
+  spv_validator_options validator_options_;
 };
 
 }  // namespace fuzz

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

@@ -43,6 +43,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
     5, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingOpPhiSynonym = {5, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
@@ -69,6 +70,7 @@ const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
     30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
     {50, 95};
+const std::pair<uint32_t, uint32_t> kChanceOfInliningFunction = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
     10, 90};
 const std::pair<uint32_t, uint32_t>
@@ -181,6 +183,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
       ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
   chance_of_adding_no_contraction_decoration_ =
       ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
+  chance_of_adding_opphi_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym);
   chance_of_adding_parameters =
       ChooseBetweenMinAndMax(kChanceOfAddingParameters);
   chance_of_adding_relaxed_decoration_ =
@@ -216,6 +220,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
   chance_of_going_deeper_when_making_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+  chance_of_inlining_function_ =
+      ChooseBetweenMinAndMax(kChanceOfInliningFunction);
   chance_of_interchanging_signedness_of_integer_operands_ =
       ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands);
   chance_of_interchanging_zero_like_constants_ =

+ 8 - 0
3rdparty/spirv-tools/source/fuzz/fuzzer_context.h

@@ -148,6 +148,9 @@ class FuzzerContext {
   uint32_t GetChanceOfAddingNoContractionDecoration() {
     return chance_of_adding_no_contraction_decoration_;
   }
+  uint32_t GetChanceOfAddingOpPhiSynonym() {
+    return chance_of_adding_opphi_synonym_;
+  }
   uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
   uint32_t GetChanceOfAddingRelaxedDecoration() {
     return chance_of_adding_relaxed_decoration_;
@@ -195,6 +198,9 @@ class FuzzerContext {
   uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
     return chance_of_going_deeper_when_making_access_chain_;
   }
+  uint32_t GetChanceOfInliningFunction() {
+    return chance_of_inlining_function_;
+  }
   uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() {
     return chance_of_interchanging_signedness_of_integer_operands_;
   }
@@ -364,6 +370,7 @@ class FuzzerContext {
   uint32_t chance_of_adding_loop_preheader_;
   uint32_t chance_of_adding_matrix_type_;
   uint32_t chance_of_adding_no_contraction_decoration_;
+  uint32_t chance_of_adding_opphi_synonym_;
   uint32_t chance_of_adding_parameters;
   uint32_t chance_of_adding_relaxed_decoration_;
   uint32_t chance_of_adding_store_;
@@ -383,6 +390,7 @@ class FuzzerContext {
   uint32_t chance_of_donating_additional_module_;
   uint32_t chance_of_going_deeper_to_insert_in_composite_;
   uint32_t chance_of_going_deeper_when_making_access_chain_;
+  uint32_t chance_of_inlining_function_;
   uint32_t chance_of_interchanging_signedness_of_integer_operands_;
   uint32_t chance_of_interchanging_zero_like_constants_;
   uint32_t chance_of_inverting_comparison_operators_;

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

@@ -296,8 +296,6 @@ uint32_t FuzzerPass::FindOrCreateIntegerConstant(
 uint32_t FuzzerPass::FindOrCreateFloatConstant(
     const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant) {
   auto float_type_id = FindOrCreateFloatType(width);
-  opt::analysis::FloatConstant float_constant(
-      GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(), words);
   if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
           GetIRContext(), *GetTransformationContext(), words, float_type_id,
           is_irrelevant)) {

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

@@ -0,0 +1,297 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_opphi_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default;
+
+void FuzzerPassAddOpPhiSynonyms::Apply() {
+  // Get a list of synonymous ids with the same type that can be used in the
+  // same OpPhi instruction.
+  auto equivalence_classes = GetIdEquivalenceClasses();
+
+  // Make a list of references, to avoid copying sets unnecessarily.
+  std::vector<std::set<uint32_t>*> equivalence_class_pointers;
+  for (auto& set : equivalence_classes) {
+    equivalence_class_pointers.push_back(&set);
+  }
+
+  // Keep a list of transformations to apply at the end.
+  std::vector<TransformationAddOpPhiSynonym> transformations_to_apply;
+
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // Randomly decide whether to consider this block.
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) {
+        continue;
+      }
+
+      // The block must have at least one predecessor.
+      size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size();
+      if (num_preds == 0) {
+        continue;
+      }
+
+      std::set<uint32_t>* chosen_equivalence_class = nullptr;
+
+      if (num_preds > 1) {
+        // If the block has more than one predecessor, prioritise sets with at
+        // least 2 ids available at some predecessor.
+        chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
+            equivalence_class_pointers, block.id(), 2);
+      }
+
+      // If a set was not already chosen, choose one with at least one available
+      // id.
+      if (!chosen_equivalence_class) {
+        chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
+            equivalence_class_pointers, block.id(), 1);
+      }
+
+      // If no suitable set was found, we cannot apply the transformation to
+      // this block.
+      if (!chosen_equivalence_class) {
+        continue;
+      }
+
+      // Initialise the map from predecessor labels to ids.
+      std::map<uint32_t, uint32_t> preds_to_ids;
+
+      // Keep track of the ids used and of the id of a predecessor with at least
+      // two ids to choose from. This is to ensure that, if possible, at least
+      // two distinct ids will be used.
+      std::set<uint32_t> ids_chosen;
+      uint32_t pred_with_alternatives = 0;
+
+      // Choose an id for each predecessor.
+      for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) {
+        auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id);
+        assert(!suitable_ids.empty() &&
+               "We must be able to find at least one suitable id because the "
+               "equivalence class was chosen among suitable ones.");
+
+        // If this predecessor has more than one id to choose from and it is the
+        // first one of this kind that we found, remember its id.
+        if (suitable_ids.size() > 1 && !pred_with_alternatives) {
+          pred_with_alternatives = pred_id;
+        }
+
+        uint32_t chosen_id =
+            suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)];
+
+        // Add this id to the set of ids chosen.
+        ids_chosen.emplace(chosen_id);
+
+        // Add the pair (predecessor, chosen id) to the map.
+        preds_to_ids[pred_id] = chosen_id;
+      }
+
+      // If:
+      // - the block has more than one predecessor
+      // - at least one predecessor has more than one alternative
+      // - the same id has been chosen by all the predecessors
+      // then choose another one for the predecessor with more than one
+      // alternative.
+      if (num_preds > 1 && pred_with_alternatives != 0 &&
+          ids_chosen.size() == 1) {
+        auto suitable_ids =
+            GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives);
+        uint32_t chosen_id =
+            GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
+        if (chosen_id == preds_to_ids[pred_with_alternatives]) {
+          chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
+        }
+
+        preds_to_ids[pred_with_alternatives] = chosen_id;
+      }
+
+      // Add the transformation to the list of transformations to apply.
+      transformations_to_apply.emplace_back(block.id(), preds_to_ids,
+                                            GetFuzzerContext()->GetFreshId());
+    }
+  }
+
+  // Apply the transformations.
+  for (const auto& transformation : transformations_to_apply) {
+    ApplyTransformation(transformation);
+  }
+}
+
+std::vector<std::set<uint32_t>>
+FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() {
+  std::vector<std::set<uint32_t>> id_equivalence_classes;
+
+  // Keep track of all the ids that have already be assigned to a class.
+  std::set<uint32_t> already_in_a_class;
+
+  for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
+    // Exclude ids that have already been assigned to a class.
+    if (already_in_a_class.count(pair.first)) {
+      continue;
+    }
+
+    // Exclude irrelevant ids.
+    if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+            pair.first)) {
+      continue;
+    }
+
+    // Exclude ids having a type that is not allowed by the transformation.
+    if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
+            GetIRContext(), pair.second->type_id())) {
+      continue;
+    }
+
+    // We need a new equivalence class for this id.
+    std::set<uint32_t> new_equivalence_class;
+
+    // Add this id to the class.
+    new_equivalence_class.emplace(pair.first);
+    already_in_a_class.emplace(pair.first);
+
+    // Add all the synonyms with the same type to this class.
+    for (auto synonym :
+         GetTransformationContext()->GetFactManager()->GetSynonymsForId(
+             pair.first)) {
+      // The synonym must be a plain id - it cannot be an indexed access into a
+      // composite.
+      if (synonym->index_size() > 0) {
+        continue;
+      }
+
+      // The synonym must not be irrelevant.
+      if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+              synonym->object())) {
+        continue;
+      }
+
+      auto synonym_def =
+          GetIRContext()->get_def_use_mgr()->GetDef(synonym->object());
+      // The synonym must exist and have the same type as the id we are
+      // considering.
+      if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) {
+        continue;
+      }
+
+      // We can add this synonym to the new equivalence class.
+      new_equivalence_class.emplace(synonym->object());
+      already_in_a_class.emplace(synonym->object());
+    }
+
+    // Add the new equivalence class to the list of equivalence classes.
+    id_equivalence_classes.emplace_back(std::move(new_equivalence_class));
+  }
+
+  return id_equivalence_classes;
+}
+
+bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock(
+    const std::set<uint32_t>& equivalence_class, uint32_t block_id,
+    uint32_t distinct_ids_required) {
+  bool at_least_one_id_for_each_pred = true;
+
+  // Keep a set of the suitable ids found.
+  std::set<uint32_t> suitable_ids_found;
+
+  // Loop through all the predecessors of the block.
+  for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) {
+    // Find the last instruction in the predecessor block.
+    auto last_instruction =
+        GetIRContext()->get_instr_block(pred_id)->terminator();
+
+    // Initially assume that there is not a suitable id for this predecessor.
+    bool at_least_one_suitable_id_found = false;
+    for (uint32_t id : equivalence_class) {
+      if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(),
+                                                     last_instruction, id)) {
+        // We have found a suitable id.
+        at_least_one_suitable_id_found = true;
+        suitable_ids_found.emplace(id);
+
+        // If we have already found enough distinct suitable ids, we don't need
+        // to check the remaining ones for this predecessor.
+        if (suitable_ids_found.size() >= distinct_ids_required) {
+          break;
+        }
+      }
+    }
+    // If no suitable id was found for this predecessor, this equivalence class
+    // is not suitable and we don't need to check the other predecessors.
+    if (!at_least_one_suitable_id_found) {
+      at_least_one_id_for_each_pred = false;
+      break;
+    }
+  }
+
+  // The equivalence class is suitable if at least one suitable id was found for
+  // each predecessor and we have found at least |distinct_ids_required|
+  // distinct suitable ids in general.
+  return at_least_one_id_for_each_pred &&
+         suitable_ids_found.size() >= distinct_ids_required;
+}
+
+std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds(
+    const std::set<uint32_t>& ids, uint32_t pred_id) {
+  // Initialise an empty vector of suitable ids.
+  std::vector<uint32_t> suitable_ids;
+
+  // Get the predecessor block.
+  auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id);
+
+  // Loop through the ids to find the suitable ones.
+  for (uint32_t id : ids) {
+    if (fuzzerutil::IdIsAvailableBeforeInstruction(
+            GetIRContext(), predecessor->terminator(), id)) {
+      suitable_ids.push_back(id);
+    }
+  }
+
+  return suitable_ids;
+}
+
+std::set<uint32_t>*
+FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly(
+    const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
+    uint32_t distinct_ids_required) {
+  auto remaining_candidates = candidates;
+  while (!remaining_candidates.empty()) {
+    // Choose one set randomly and return it if it is suitable.
+    auto chosen =
+        GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates);
+    if (EquivalenceClassIsSuitableForBlock(*chosen, block_id,
+                                           distinct_ids_required)) {
+      return chosen;
+    }
+  }
+
+  // No suitable sets were found.
+  return nullptr;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 73 - 0
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_opphi_synonyms.h

@@ -0,0 +1,73 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass to add OpPhi instructions which can take the values of ids that
+// have been marked as synonymous. This instruction will itself be marked as
+// synonymous with the others.
+class FuzzerPassAddOpPhiSynonyms : public FuzzerPass {
+ public:
+  FuzzerPassAddOpPhiSynonyms(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddOpPhiSynonyms() override;
+
+  void Apply() override;
+
+  // Computes the equivalence classes for the non-pointer and non-irrelevant ids
+  // in the module, where two ids are considered equivalent iff they have been
+  // declared synonymous and they have the same type.
+  std::vector<std::set<uint32_t>> GetIdEquivalenceClasses();
+
+  // Returns true iff |equivalence_class| contains at least
+  // |distinct_ids_required| ids so that all of these ids are available at the
+  // end of at least one predecessor of the block with label |block_id|.
+  // Assumes that the block has at least one predecessor.
+  bool EquivalenceClassIsSuitableForBlock(
+      const std::set<uint32_t>& equivalence_class, uint32_t block_id,
+      uint32_t distinct_ids_required);
+
+  // Returns a vector with the ids that are available to use at the end of the
+  // block with id |pred_id|, selected among the given |ids|. Assumes that
+  // |pred_id| is the label of a block and all ids in |ids| exist in the module.
+  std::vector<uint32_t> GetSuitableIds(const std::set<uint32_t>& ids,
+                                       uint32_t pred_id);
+
+ private:
+  // Randomly chooses one of the equivalence classes in |candidates|, so that it
+  // satisfies all of the following conditions:
+  // - For each of the predecessors of the |block_id| block, there is at least
+  //   one id in the chosen equivalence class that is available at the end of
+  //   it.
+  // - There are at least |distinct_ids_required| ids available at the end of
+  //   some predecessor.
+  // Returns nullptr if no equivalence class in |candidates| satisfies the
+  // requirements.
+  std::set<uint32_t>* MaybeFindSuitableEquivalenceClassRandomly(
+      const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
+      uint32_t distinct_ids_required);
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_

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

@@ -0,0 +1,104 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_inline_functions.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_inline_function.h"
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassInlineFunctions::FuzzerPassInlineFunctions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default;
+
+void FuzzerPassInlineFunctions::Apply() {
+  // |function_call_instructions| are the instructions that will be inlined.
+  // First, they will be collected and then do the inlining in another loop.
+  // This avoids changing the module while it is being inspected.
+  std::vector<opt::Instruction*> function_call_instructions;
+
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfInliningFunction())) {
+          continue;
+        }
+
+        // |instruction| must be suitable for inlining.
+        if (!TransformationInlineFunction::IsSuitableForInlining(
+                GetIRContext(), &instruction)) {
+          continue;
+        }
+
+        function_call_instructions.push_back(&instruction);
+      }
+    }
+  }
+
+  // Once the function calls have been collected, it's time to actually create
+  // and apply the inlining transformations.
+  for (auto& function_call_instruction : function_call_instructions) {
+    // If |function_call_instruction| is not the penultimate instruction in its
+    // block or its block termination instruction is not OpBranch, then try to
+    // split |function_call_block| such that the conditions are met.
+    auto* function_call_block =
+        GetIRContext()->get_instr_block(function_call_instruction);
+    if ((function_call_instruction != &*--function_call_block->tail() ||
+         function_call_block->terminator()->opcode() != SpvOpBranch) &&
+        !MaybeApplyTransformation(TransformationSplitBlock(
+            MakeInstructionDescriptor(GetIRContext(),
+                                      function_call_instruction->NextNode()),
+            GetFuzzerContext()->GetFreshId()))) {
+      continue;
+    }
+
+    auto* called_function = fuzzerutil::FindFunction(
+        GetIRContext(), function_call_instruction->GetSingleWordInOperand(0));
+
+    // Mapping the called function instructions.
+    std::map<uint32_t, uint32_t> result_id_map;
+    for (auto& called_function_block : *called_function) {
+      // The called function entry block label will not be inlined.
+      if (&called_function_block != &*called_function->entry()) {
+        result_id_map[called_function_block.GetLabelInst()->result_id()] =
+            GetFuzzerContext()->GetFreshId();
+      }
+
+      for (auto& instruction_to_inline : called_function_block) {
+        // The instructions are mapped to fresh ids.
+        if (instruction_to_inline.HasResultId()) {
+          result_id_map[instruction_to_inline.result_id()] =
+              GetFuzzerContext()->GetFreshId();
+        }
+      }
+    }
+
+    // Applies the inline function transformation.
+    ApplyTransformation(TransformationInlineFunction(
+        function_call_instruction->result_id(), result_id_map));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 41 - 0
3rdparty/spirv-tools/source/fuzz/fuzzer_pass_inline_functions.h

@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Looks for OpFunctionCall instructions and randomly decides which ones to
+// inline. If the instructions of the called function are going to be inlined,
+// then a mapping, between their result ids and suitable ids, is done.
+class FuzzerPassInlineFunctions : public FuzzerPass {
+ public:
+  FuzzerPassInlineFunctions(opt::IRContext* ir_context,
+                            TransformationContext* transformation_context,
+                            FuzzerContext* fuzzer_context,
+                            protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassInlineFunctions() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_

+ 27 - 39
3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp

@@ -271,6 +271,11 @@ bool CanMakeSynonymOf(opt::IRContext* ir_context,
     return false;
   }
   auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id());
+  if (type_inst->opcode() == SpvOpTypeVoid) {
+    // We only make synonyms of instructions that define objects, and an object
+    // cannot have void type.
+    return false;
+  }
   if (type_inst->opcode() == SpvOpTypePointer) {
     switch (inst->opcode()) {
       case SpvOpConstantNull:
@@ -970,35 +975,34 @@ uint32_t MaybeGetZeroConstant(
     opt::IRContext* ir_context,
     const TransformationContext& transformation_context,
     uint32_t scalar_or_composite_type_id, bool is_irrelevant) {
-  const auto* type =
-      ir_context->get_type_mgr()->GetType(scalar_or_composite_type_id);
-  assert(type && "|scalar_or_composite_type_id| is invalid");
+  const auto* type_inst =
+      ir_context->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
+  assert(type_inst && "|scalar_or_composite_type_id| is invalid");
 
-  switch (type->kind()) {
-    case opt::analysis::Type::kBool:
+  switch (type_inst->opcode()) {
+    case SpvOpTypeBool:
       return MaybeGetBoolConstant(ir_context, transformation_context, false,
                                   is_irrelevant);
-    case opt::analysis::Type::kFloat:
-    case opt::analysis::Type::kInteger: {
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt: {
+      const auto width = type_inst->GetSingleWordInOperand(0);
       std::vector<uint32_t> words = {0};
-      if ((type->AsInteger() && type->AsInteger()->width() > 32) ||
-          (type->AsFloat() && type->AsFloat()->width() > 32)) {
+      if (width > 32) {
         words.push_back(0);
       }
 
       return MaybeGetScalarConstant(ir_context, transformation_context, words,
                                     scalar_or_composite_type_id, is_irrelevant);
     }
-    case opt::analysis::Type::kStruct: {
+    case SpvOpTypeStruct: {
       std::vector<uint32_t> component_ids;
-      for (const auto* component_type : type->AsStruct()->element_types()) {
-        auto component_type_id =
-            ir_context->get_type_mgr()->GetId(component_type);
-        assert(component_type_id && "Component type is invalid");
+      for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+        const auto component_type_id = type_inst->GetSingleWordInOperand(i);
 
         auto component_id =
             MaybeGetZeroConstant(ir_context, transformation_context,
                                  component_type_id, is_irrelevant);
+
         if (component_id == 0 && is_irrelevant) {
           // Irrelevant constants can use either relevant or irrelevant
           // constituents.
@@ -1017,14 +1021,9 @@ uint32_t MaybeGetZeroConstant(
           ir_context, transformation_context, component_ids,
           scalar_or_composite_type_id, is_irrelevant);
     }
-    case opt::analysis::Type::kMatrix:
-    case opt::analysis::Type::kVector: {
-      const auto* component_type = type->AsVector()
-                                       ? type->AsVector()->element_type()
-                                       : type->AsMatrix()->element_type();
-      auto component_type_id =
-          ir_context->get_type_mgr()->GetId(component_type);
-      assert(component_type_id && "Component type is invalid");
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector: {
+      const auto component_type_id = type_inst->GetSingleWordInOperand(0);
 
       auto component_id = MaybeGetZeroConstant(
           ir_context, transformation_context, component_type_id, is_irrelevant);
@@ -1040,23 +1039,21 @@ uint32_t MaybeGetZeroConstant(
         return 0;
       }
 
-      auto component_count = type->AsVector()
-                                 ? type->AsVector()->element_count()
-                                 : type->AsMatrix()->element_count();
+      const auto component_count = type_inst->GetSingleWordInOperand(1);
       return MaybeGetCompositeConstant(
           ir_context, transformation_context,
           std::vector<uint32_t>(component_count, component_id),
           scalar_or_composite_type_id, is_irrelevant);
     }
-    case opt::analysis::Type::kArray: {
-      auto component_type_id =
-          ir_context->get_type_mgr()->GetId(type->AsArray()->element_type());
-      assert(component_type_id && "Component type is invalid");
+    case SpvOpTypeArray: {
+      const auto component_type_id = type_inst->GetSingleWordInOperand(0);
 
       auto component_id = MaybeGetZeroConstant(
           ir_context, transformation_context, component_type_id, is_irrelevant);
 
       if (component_id == 0 && is_irrelevant) {
+        // Irrelevant constants can use either relevant or irrelevant
+        // constituents.
         component_id = MaybeGetZeroConstant(ir_context, transformation_context,
                                             component_type_id, false);
       }
@@ -1065,12 +1062,6 @@ uint32_t MaybeGetZeroConstant(
         return 0;
       }
 
-      auto type_id = ir_context->get_type_mgr()->GetId(type);
-      assert(type_id && "|type| is invalid");
-
-      const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(type_id);
-      assert(type_inst && "Array's type id is invalid");
-
       return MaybeGetCompositeConstant(
           ir_context, transformation_context,
           std::vector<uint32_t>(GetArraySize(*type_inst, ir_context),
@@ -1113,10 +1104,7 @@ uint32_t MaybeGetCompositeConstant(
     bool is_irrelevant) {
   const auto* type = ir_context->get_type_mgr()->GetType(composite_type_id);
   (void)type;  // Make compilers happy in release mode.
-  assert(type &&
-         (type->AsArray() || type->AsStruct() || type->AsVector() ||
-          type->AsMatrix()) &&
-         "|composite_type_id| is invalid");
+  assert(IsCompositeType(type) && "|composite_type_id| is invalid");
 
   for (const auto& inst : ir_context->types_values()) {
     if (inst.opcode() == SpvOpConstantComposite &&

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

@@ -0,0 +1,23 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+OverflowIdSource::~OverflowIdSource() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 106 - 0
3rdparty/spirv-tools/source/fuzz/overflow_id_source.h

@@ -0,0 +1,106 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_
+#define SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_
+
+#include <cstdint>
+
+namespace spvtools {
+namespace fuzz {
+
+// An implementation of this interface can be used to provide fresh ids on
+// demand when applying a transformation.
+//
+// During fuzzing this should never be required: a fuzzer pass should determine
+// all the fresh ids it requires to apply a transformation.
+//
+// However, during shrinking we can have the situation where, after removing
+// an early transformation, a later transformation needs more ids.
+//
+// As an example, suppose a SPIR-V function originally has this form:
+//
+// main() {
+//   stmt1;
+//   stmt2;
+//   stmt3;
+//   stmt4;
+// }
+//
+// Now suppose two *outlining* transformations are applied.  The first
+// transformation, T1, outlines "stmt1; stmt2;" into a function foo, giving us:
+//
+// foo() {
+//   stmt1;
+//   stmt2;
+// }
+//
+// main() {
+//   foo();
+//   stmt3;
+//   stmt4;
+// }
+//
+// The second transformation, T2, outlines "foo(); stmt3;" from main into a
+// function bar, giving us:
+//
+// foo() {
+//   stmt1;
+//   stmt2;
+// }
+//
+// bar() {
+//   foo();
+//   stmt3;
+// }
+//
+// main() {
+//   bar();
+//   stmt4;
+// }
+//
+// Suppose that T2 used a set of fresh ids, FRESH, in order to perform its
+// outlining.
+//
+// Now suppose that during shrinking we remove T1, but still want to apply T2.
+// The fresh ids used by T2 - FRESH - are sufficient to outline "foo(); stmt3;".
+// However, because we did not apply T1, "foo();" does not exist and instead the
+// task of T2 is to outline "stmt1; stmt2; stmt3;".  The set FRESH contains
+// *some* of the fresh ids required to do this (those for "stmt3;"), but not all
+// of them (those for "stmt1; stmt2;" are missing).
+//
+// A source of overflow ids can be used to allow the shrinker to proceed
+// nevertheless.
+//
+// It is desirable to use overflow ids only when needed.  In our worked example,
+// T2 should still use the ids from FRESH when handling "stmt3;", because later
+// transformations might refer to those ids and will become inapplicable if
+// overflow ids are used instead.
+class OverflowIdSource {
+ public:
+  virtual ~OverflowIdSource();
+
+  // Returns true if and only if this source is capable of providing overflow
+  // ids.
+  virtual bool HasOverflowIds() const = 0;
+
+  // Precondition: HasOverflowIds() must hold.  Returns the next available
+  // overflow id.
+  virtual uint32_t GetNextOverflowId() = 0;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_

+ 34 - 0
3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto

@@ -413,6 +413,8 @@ message Transformation {
     TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
     TransformationPropagateInstructionUp propagate_instruction_up = 67;
     TransformationCompositeInsert composite_insert = 68;
+    TransformationInlineFunction inline_function = 69;
+    TransformationAddOpPhiSynonym add_opphi_synonym = 70;
     // Add additional option using the next available number.
   }
 }
@@ -745,6 +747,24 @@ message TransformationAddNoContractionDecoration {
 
 }
 
+message TransformationAddOpPhiSynonym {
+
+  // Adds an OpPhi instruction at the start of a block with n predecessors (pred_1, pred_2, ..., pred_n)
+  // and n related ids (id_1, id_2, ..., id_n) which are pairwise synonymous.
+  // The instruction will be of the form:
+  //       %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
+  // and fresh_id will be recorded as being synonymous with all the other ids.
+
+  // Label id of the block
+  uint32 block_id = 1;
+
+  // Pairs (pred_i, id_i)
+  repeated UInt32Pair pred_to_id = 2;
+
+  // Fresh id for the new instruction
+  uint32 fresh_id = 3;
+}
+
 message TransformationAddParameter {
 
   // Adds a new parameter into the function.
@@ -1103,6 +1123,20 @@ message TransformationFunctionCall {
 
 }
 
+message TransformationInlineFunction {
+
+  // This transformation inlines a function by mapping the function instructions to fresh ids.
+
+  // Result id of the function call instruction.
+  uint32 function_call_id = 1;
+
+  // For each result id defined by the called function,
+  // this map provides an associated fresh id that can
+  // be used in the inlined version of the function call.
+  repeated UInt32Pair result_id_map = 2;
+
+}
+
 message TransformationInvertComparisonOperator {
 
   // For some instruction with result id |operator_id| that

+ 47 - 54
3rdparty/spirv-tools/source/fuzz/replayer.cpp

@@ -14,58 +14,38 @@
 
 #include "source/fuzz/replayer.h"
 
+#include <memory>
 #include <utility>
 
-#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
-#include "source/fuzz/transformation_add_constant_boolean.h"
-#include "source/fuzz/transformation_add_constant_scalar.h"
-#include "source/fuzz/transformation_add_dead_break.h"
-#include "source/fuzz/transformation_add_type_boolean.h"
-#include "source/fuzz/transformation_add_type_float.h"
-#include "source/fuzz/transformation_add_type_int.h"
-#include "source/fuzz/transformation_add_type_pointer.h"
 #include "source/fuzz/transformation_context.h"
-#include "source/fuzz/transformation_move_block_down.h"
-#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
-#include "source/fuzz/transformation_replace_constant_with_uniform.h"
-#include "source/fuzz/transformation_split_block.h"
 #include "source/opt/build_module.h"
 #include "source/util/make_unique.h"
 
 namespace spvtools {
 namespace fuzz {
 
-struct Replayer::Impl {
-  Impl(spv_target_env env, bool validate, spv_validator_options options)
-      : target_env(env),
-        validate_during_replay(validate),
-        validator_options(options) {}
-
-  const spv_target_env target_env;    // Target environment.
-  MessageConsumer consumer;           // Message consumer.
-  const bool validate_during_replay;  // Controls whether the validator should
-                                      // be run after every replay step.
-  spv_validator_options validator_options;  // Options to control
-                                            // validation
-};
-
-Replayer::Replayer(spv_target_env env, bool validate_during_replay,
+Replayer::Replayer(spv_target_env target_env, bool validate_during_replay,
                    spv_validator_options validator_options)
-    : impl_(MakeUnique<Impl>(env, validate_during_replay, validator_options)) {}
+    : target_env_(target_env),
+      validate_during_replay_(validate_during_replay),
+      validator_options_(validator_options) {}
 
 Replayer::~Replayer() = default;
 
-void Replayer::SetMessageConsumer(MessageConsumer c) {
-  impl_->consumer = std::move(c);
+void Replayer::SetMessageConsumer(MessageConsumer consumer) {
+  consumer_ = std::move(consumer);
 }
 
 Replayer::ReplayerResultStatus Replayer::Run(
     const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
     const protobufs::TransformationSequence& transformation_sequence_in,
-    uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
+    uint32_t num_transformations_to_apply, uint32_t first_overflow_id,
+    std::vector<uint32_t>* binary_out,
     protobufs::TransformationSequence* transformation_sequence_out) const {
   // Check compatibility between the library version being linked with and the
   // header files being used.
@@ -73,43 +53,51 @@ Replayer::ReplayerResultStatus Replayer::Run(
 
   if (num_transformations_to_apply >
       static_cast<uint32_t>(transformation_sequence_in.transformation_size())) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "The number of transformations to be replayed must not "
-                    "exceed the size of the transformation sequence.");
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "The number of transformations to be replayed must not "
+              "exceed the size of the transformation sequence.");
     return Replayer::ReplayerResultStatus::kTooManyTransformationsRequested;
   }
 
-  spvtools::SpirvTools tools(impl_->target_env);
+  spvtools::SpirvTools tools(target_env_);
   if (!tools.IsValid()) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Failed to create SPIRV-Tools interface; stopping.");
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "Failed to create SPIRV-Tools interface; stopping.");
     return Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface;
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size(),
-                      impl_->validator_options)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is invalid; stopping.");
+  if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is invalid; stopping.");
     return Replayer::ReplayerResultStatus::kInitialBinaryInvalid;
   }
 
   // Build the module from the input binary.
-  std::unique_ptr<opt::IRContext> ir_context = BuildModule(
-      impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+  std::unique_ptr<opt::IRContext> ir_context =
+      BuildModule(target_env_, consumer_, binary_in.data(), binary_in.size());
   assert(ir_context);
 
   // For replay validation, we track the last valid SPIR-V binary that was
   // observed. Initially this is the input binary.
   std::vector<uint32_t> last_valid_binary;
-  if (impl_->validate_during_replay) {
+  if (validate_during_replay_) {
     last_valid_binary = binary_in;
   }
 
   FactManager fact_manager;
-  fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
-  TransformationContext transformation_context(&fact_manager,
-                                               impl_->validator_options);
+  fact_manager.AddFacts(consumer_, initial_facts, ir_context.get());
+  std::unique_ptr<TransformationContext> transformation_context =
+      first_overflow_id == 0
+          ? MakeUnique<TransformationContext>(&fact_manager, validator_options_)
+          : MakeUnique<TransformationContext>(
+                &fact_manager, validator_options_,
+                MakeUnique<CounterOverflowIdSource>(first_overflow_id));
+
+  // We track the largest id bound observed, to ensure that it only increases
+  // as transformations are applied.
+  uint32_t max_observed_id_bound = ir_context->module()->id_bound();
+  (void)(max_observed_id_bound);  // Keep release-mode compilers happy.
 
   // Consider the transformation proto messages in turn.
   uint32_t counter = 0;
@@ -123,22 +111,27 @@ Replayer::ReplayerResultStatus Replayer::Run(
 
     // Check whether the transformation can be applied.
     if (transformation->IsApplicable(ir_context.get(),
-                                     transformation_context)) {
+                                     *transformation_context)) {
       // The transformation is applicable, so apply it, and copy it to the
       // sequence of transformations that were applied.
-      transformation->Apply(ir_context.get(), &transformation_context);
+      transformation->Apply(ir_context.get(), transformation_context.get());
       *transformation_sequence_out->add_transformation() = message;
 
-      if (impl_->validate_during_replay) {
+      assert(ir_context->module()->id_bound() >= max_observed_id_bound &&
+             "The module's id bound should only increase due to applying "
+             "transformations.");
+      max_observed_id_bound = ir_context->module()->id_bound();
+
+      if (validate_during_replay_) {
         std::vector<uint32_t> binary_to_validate;
         ir_context->module()->ToBinary(&binary_to_validate, false);
 
         // Check whether the latest transformation led to a valid binary.
         if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
-                            impl_->validator_options)) {
-          impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                          "Binary became invalid during replay (set a "
-                          "breakpoint to inspect); stopping.");
+                            validator_options_)) {
+          consumer_(SPV_MSG_INFO, nullptr, {},
+                    "Binary became invalid during replay (set a "
+                    "breakpoint to inspect); stopping.");
           return Replayer::ReplayerResultStatus::kReplayValidationFailure;
         }
 

+ 25 - 7
3rdparty/spirv-tools/source/fuzz/replayer.h

@@ -38,7 +38,7 @@ class Replayer {
   };
 
   // Constructs a replayer from the given target environment.
-  Replayer(spv_target_env env, bool validate_during_replay,
+  Replayer(spv_target_env target_env, bool validate_during_replay,
            spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
@@ -55,20 +55,38 @@ class Replayer {
 
   // Transforms |binary_in| to |binary_out| by attempting to apply the first
   // |num_transformations_to_apply| transformations from
-  // |transformation_sequence_in|.  Initial facts about the input binary and the
-  // context in which it will execute are provided via |initial_facts|.  The
-  // transformations that were successfully applied are returned via
+  // |transformation_sequence_in|.
+  //
+  // Initial facts about the input binary and the context in which it will
+  // execute are provided via |initial_facts|.
+  //
+  // |first_overflow_id| should be set to 0 if overflow ids are not available
+  // during replay.  Otherwise |first_overflow_id| must be larger than any id
+  // referred to in |binary_in| or |transformation_sequence_in|, and overflow
+  // ids will be available during replay starting from this value.
+  //
+  // The transformations that were successfully applied are returned via
   // |transformation_sequence_out|.
   ReplayerResultStatus Run(
       const std::vector<uint32_t>& binary_in,
       const protobufs::FactSequence& initial_facts,
       const protobufs::TransformationSequence& transformation_sequence_in,
-      uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
+      uint32_t num_transformations_to_apply, uint32_t first_overflow_id,
+      std::vector<uint32_t>* binary_out,
       protobufs::TransformationSequence* transformation_sequence_out) const;
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  // Target environment.
+  const spv_target_env target_env_;
+
+  // Message consumer.
+  MessageConsumer consumer_;
+
+  // Controls whether the validator should be run after every replay step.
+  const bool validate_during_replay_;
+
+  // Options to control validation
+  spv_validator_options validator_options_;
 };
 
 }  // namespace fuzz

+ 41 - 41
3rdparty/spirv-tools/source/fuzz/shrinker.cpp

@@ -18,6 +18,8 @@
 
 #include "source/fuzz/pseudo_random_generator.h"
 #include "source/fuzz/replayer.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
 #include "source/spirv_fuzzer_options.h"
 #include "source/util/make_unique.h"
 
@@ -59,33 +61,26 @@ protobufs::TransformationSequence RemoveChunk(
 
 }  // namespace
 
-struct Shrinker::Impl {
-  Impl(spv_target_env env, uint32_t limit, bool validate,
-       spv_validator_options options)
-      : target_env(env),
-        step_limit(limit),
-        validate_during_replay(validate),
-        validator_options(options) {}
-
-  const spv_target_env target_env;          // Target environment.
-  MessageConsumer consumer;                 // Message consumer.
-  const uint32_t step_limit;                // Step limit for reductions.
-  const bool validate_during_replay;        // Determines whether to check for
-                                            // validity during the replaying of
-                                            // transformations.
-  spv_validator_options validator_options;  // Options to control validation.
-};
+uint32_t Shrinker::GetIdBound(const std::vector<uint32_t>& binary) const {
+  // Build the module from the input binary.
+  std::unique_ptr<opt::IRContext> ir_context =
+      BuildModule(target_env_, consumer_, binary.data(), binary.size());
+  assert(ir_context && "Error building module.");
+  return ir_context->module()->id_bound();
+}
 
-Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
+Shrinker::Shrinker(spv_target_env target_env, uint32_t step_limit,
                    bool validate_during_replay,
                    spv_validator_options validator_options)
-    : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
-                             validator_options)) {}
+    : target_env_(target_env),
+      step_limit_(step_limit),
+      validate_during_replay_(validate_during_replay),
+      validator_options_(validator_options) {}
 
 Shrinker::~Shrinker() = default;
 
-void Shrinker::SetMessageConsumer(MessageConsumer c) {
-  impl_->consumer = std::move(c);
+void Shrinker::SetMessageConsumer(MessageConsumer consumer) {
+  consumer_ = std::move(consumer);
 }
 
 Shrinker::ShrinkerResultStatus Shrinker::Run(
@@ -99,18 +94,17 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
   // header files being used.
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-  spvtools::SpirvTools tools(impl_->target_env);
+  SpirvTools tools(target_env_);
   if (!tools.IsValid()) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Failed to create SPIRV-Tools interface; stopping.");
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "Failed to create SPIRV-Tools interface; stopping.");
     return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface;
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size(),
-                      impl_->validator_options)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is invalid; stopping.");
+  if (!tools.Validate(&binary_in[0], binary_in.size(), validator_options_)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is invalid; stopping.");
     return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid;
   }
 
@@ -121,13 +115,13 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
   // succeeds, (b) get the binary that results from running these
   // transformations, and (c) get the subsequence of the initial transformations
   // that actually apply (in principle this could be a strict subsequence).
-  Replayer replayer(impl_->target_env, impl_->validate_during_replay,
-                    impl_->validator_options);
-  replayer.SetMessageConsumer(impl_->consumer);
+  Replayer replayer(target_env_, validate_during_replay_, validator_options_);
+  replayer.SetMessageConsumer(consumer_);
   if (replayer.Run(binary_in, initial_facts, transformation_sequence_in,
                    static_cast<uint32_t>(
                        transformation_sequence_in.transformation_size()),
-                   &current_best_binary, &current_best_transformations) !=
+                   /* No overflow ids */ 0, &current_best_binary,
+                   &current_best_transformations) !=
       Replayer::ReplayerResultStatus::kComplete) {
     return ShrinkerResultStatus::kReplayFailed;
   }
@@ -135,11 +129,17 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
   // Check that the binary produced by applying the initial transformations is
   // indeed interesting.
   if (!interestingness_function(current_best_binary, 0)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is not interesting; stopping.");
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is not interesting; stopping.");
     return ShrinkerResultStatus::kInitialBinaryNotInteresting;
   }
 
+  // The largest id used by the module before any shrinking has been applied
+  // serves as the first id that can be used for overflow purposes.
+  const uint32_t first_overflow_id = GetIdBound(current_best_binary);
+  assert(first_overflow_id >= GetIdBound(binary_in) &&
+         "Applying transformations should only increase a module's id bound.");
+
   uint32_t attempt = 0;  // Keeps track of the number of shrink attempts that
                          // have been tried, whether successful or not.
 
@@ -153,7 +153,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
   // - reach the step limit,
   // - run out of transformations to remove, or
   // - cannot make the chunk size any smaller.
-  while (attempt < impl_->step_limit &&
+  while (attempt < step_limit_ &&
          !current_best_transformations.transformation().empty() &&
          chunk_size > 0) {
     bool progress_this_round =
@@ -183,7 +183,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
     // |chunk_size|, using |chunk_index| to track which chunk to try removing
     // next.  The loop exits early if we reach the shrinking step limit.
     for (int chunk_index = num_chunks - 1;
-         attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) {
+         attempt < step_limit_ && chunk_index >= 0; chunk_index--) {
       // Remove a chunk of transformations according to the current index and
       // chunk size.
       auto transformations_with_chunk_removed =
@@ -201,7 +201,7 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
               binary_in, initial_facts, transformations_with_chunk_removed,
               static_cast<uint32_t>(
                   transformations_with_chunk_removed.transformation_size()),
-              &next_binary, &next_transformation_sequence) !=
+              first_overflow_id, &next_binary, &next_transformation_sequence) !=
           Replayer::ReplayerResultStatus::kComplete) {
         // Replay should not fail; if it does, we need to abort shrinking.
         return ShrinkerResultStatus::kReplayFailed;
@@ -244,12 +244,12 @@ Shrinker::ShrinkerResultStatus Shrinker::Run(
 
   // Indicate whether shrinking completed or was truncated due to reaching the
   // step limit.
-  assert(attempt <= impl_->step_limit);
-  if (attempt == impl_->step_limit) {
+  assert(attempt <= step_limit_);
+  if (attempt == step_limit_) {
     std::stringstream strstream;
-    strstream << "Shrinking did not complete; step limit " << impl_->step_limit
+    strstream << "Shrinking did not complete; step limit " << step_limit_
               << " was reached.";
-    impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
+    consumer_(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
     return Shrinker::ShrinkerResultStatus::kStepLimitReached;
   }
   return Shrinker::ShrinkerResultStatus::kComplete;

+ 21 - 4
3rdparty/spirv-tools/source/fuzz/shrinker.h

@@ -49,8 +49,8 @@ class Shrinker {
   using InterestingnessFunction = std::function<bool(
       const std::vector<uint32_t>& binary, uint32_t counter)>;
 
-  // Constructs a shrinker from the given target environment.
-  Shrinker(spv_target_env env, uint32_t step_limit, bool validate_during_replay,
+  Shrinker(spv_target_env target_env, uint32_t step_limit,
+           bool validate_during_replay,
            spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
@@ -82,8 +82,25 @@ class Shrinker {
       protobufs::TransformationSequence* transformation_sequence_out) const;
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  // Returns the id bound for the given SPIR-V binary, which is assumed to be
+  // valid.
+  uint32_t GetIdBound(const std::vector<uint32_t>& binary) const;
+
+  // Target environment.
+  const spv_target_env target_env_;
+
+  // Message consumer.
+  MessageConsumer consumer_;
+
+  // Step limit to decide when to terminate shrinking early.
+  const uint32_t step_limit_;
+
+  // Determines whether to check for validity during the replaying of
+  // transformations.
+  const bool validate_during_replay_;
+
+  // Options to control validation.
+  spv_validator_options validator_options_;
 };
 
 }  // namespace fuzz

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

@@ -33,6 +33,7 @@
 #include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_loop_preheader.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
+#include "source/fuzz/transformation_add_opphi_synonym.h"
 #include "source/fuzz/transformation_add_parameter.h"
 #include "source/fuzz/transformation_add_relaxed_decoration.h"
 #include "source/fuzz/transformation_add_spec_constant_op.h"
@@ -53,6 +54,7 @@
 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
 #include "source/fuzz/transformation_equation_instruction.h"
 #include "source/fuzz/transformation_function_call.h"
+#include "source/fuzz/transformation_inline_function.h"
 #include "source/fuzz/transformation_invert_comparison_operator.h"
 #include "source/fuzz/transformation_load.h"
 #include "source/fuzz/transformation_make_vector_operation_dynamic.h"
@@ -140,6 +142,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
           message.add_no_contraction_decoration());
+    case protobufs::Transformation::TransformationCase::kAddOpphiSynonym:
+      return MakeUnique<TransformationAddOpPhiSynonym>(
+          message.add_opphi_synonym());
     case protobufs::Transformation::TransformationCase::kAddParameter:
       return MakeUnique<TransformationAddParameter>(message.add_parameter());
     case protobufs::Transformation::TransformationCase::kAddRelaxedDecoration:
@@ -192,6 +197,9 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
           message.equation_instruction());
     case protobufs::Transformation::TransformationCase::kFunctionCall:
       return MakeUnique<TransformationFunctionCall>(message.function_call());
+    case protobufs::Transformation::TransformationCase::kInlineFunction:
+      return MakeUnique<TransformationInlineFunction>(
+          message.inline_function());
     case protobufs::Transformation::TransformationCase::
         kInvertComparisonOperator:
       return MakeUnique<TransformationInvertComparisonOperator>(

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

@@ -0,0 +1,200 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_opphi_synonym.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
+    const protobufs::TransformationAddOpPhiSynonym& message)
+    : message_(message) {}
+
+TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
+    uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
+    uint32_t fresh_id) {
+  message_.set_block_id(block_id);
+  *message_.mutable_pred_to_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids);
+  message_.set_fresh_id(fresh_id);
+}
+
+bool TransformationAddOpPhiSynonym::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that |message_.block_id| is a block label id.
+  auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
+  if (!block) {
+    return false;
+  }
+
+  // Check that |message_.fresh_id| is actually fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // Check that |message_.pred_to_id| contains a mapping for all of the block's
+  // predecessors.
+  std::vector<uint32_t> predecessors = ir_context->cfg()->preds(block->id());
+
+  // There must be at least one predecessor.
+  if (predecessors.empty()) {
+    return false;
+  }
+
+  std::map<uint32_t, uint32_t> preds_to_ids =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id());
+
+  // There must not be repeated key values in |message_.pred_to_id|.
+  if (preds_to_ids.size() != static_cast<size_t>(message_.pred_to_id_size())) {
+    return false;
+  }
+
+  // Check that each predecessor has a corresponding mapping and all of the
+  // corresponding ids exist.
+  for (uint32_t pred : predecessors) {
+    if (preds_to_ids.count(pred) == 0) {
+      return false;
+    }
+
+    // Check that the id exists in the module.
+    if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) {
+      return false;
+    }
+  }
+
+  // Get the first id and its type (which should be the same as all the other
+  // ones) and check that the transformation supports this type.
+  uint32_t first_id = preds_to_ids[predecessors[0]];
+  uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
+  if (!CheckTypeIsAllowed(ir_context, type_id)) {
+    return false;
+  }
+
+  // Check that the ids corresponding to predecessors are all synonymous, have
+  // the same type and are available to use at the end of the predecessor.
+  for (uint32_t pred : predecessors) {
+    auto id = preds_to_ids[pred];
+
+    // Check that the id has the same type as the other ones.
+    if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) {
+      return false;
+    }
+
+    // Check that the id is synonymous with the others by checking that it is
+    // synonymous with the first one (or it is the same id).
+    if (id != first_id &&
+        !transformation_context.GetFactManager()->IsSynonymous(
+            MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) {
+      return false;
+    }
+
+    // Check that the id is available at the end of the corresponding
+    // predecessor block.
+
+    auto pred_block = ir_context->get_instr_block(pred);
+
+    // We should always be able to find the predecessor block, since it is in
+    // the predecessors list of |block|.
+    assert(pred_block && "Could not find one of the predecessor blocks.");
+
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722): This
+    //  function always returns false if the block is unreachable, so it may
+    //  need to be refactored.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, pred_block->terminator(), id)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationAddOpPhiSynonym::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Get the type id from one of the ids.
+  uint32_t first_id = message_.pred_to_id(0).second();
+  uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
+
+  // Define the operand list.
+  opt::Instruction::OperandList operand_list;
+
+  // For each predecessor, add the corresponding operands.
+  for (auto& pair : message_.pred_to_id()) {
+    operand_list.emplace_back(
+        opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}});
+    operand_list.emplace_back(
+        opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}});
+  }
+
+  // Add a new OpPhi instructions at the beginning of the block.
+  ir_context->get_instr_block(message_.block_id())
+      ->begin()
+      .InsertBefore(MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, type_id,
+                                                 message_.fresh_id(),
+                                                 std::move(operand_list)));
+
+  // Update the module id bound.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Invalidate all analyses, since we added an instruction to the module.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Record the fact that the new id is synonym with the other ones by declaring
+  // that it is a synonym of the first one.
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.fresh_id(), {}),
+      MakeDataDescriptor(first_id, {}), ir_context);
+}
+
+protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_opphi_synonym() = message_;
+  return result;
+}
+
+bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
+    opt::IRContext* ir_context, uint32_t type_id) {
+  auto type = ir_context->get_type_mgr()->GetType(type_id);
+  if (!type) {
+    return false;
+  }
+
+  // We allow the following types: Bool, Integer, Float, Vector, Matrix, Array,
+  // Struct.
+  if (type->AsBool() || type->AsInteger() || type->AsFloat() ||
+      type->AsVector() || type->AsMatrix() || type->AsArray() ||
+      type->AsStruct()) {
+    return true;
+  }
+
+  // We allow pointer types if the VariablePointers capability is enabled and
+  // the pointer has the correct storage class (Workgroup or StorageBuffer).
+  if (type->AsPointer()) {
+    auto storage_class = type->AsPointer()->storage_class();
+    return ir_context->get_feature_mgr()->HasCapability(
+               SpvCapabilityVariablePointers) &&
+           (storage_class == SpvStorageClassWorkgroup ||
+            storage_class == SpvStorageClassStorageBuffer);
+  }
+
+  // We do not allow other types.
+  return false;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 72 - 0
3rdparty/spirv-tools/source/fuzz/transformation_add_opphi_synonym.h

@@ -0,0 +1,72 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationAddOpPhiSynonym : public Transformation {
+ public:
+  explicit TransformationAddOpPhiSynonym(
+      const protobufs::TransformationAddOpPhiSynonym& message);
+
+  TransformationAddOpPhiSynonym(
+      uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
+      uint32_t fresh_id);
+
+  // - |message_.block_id| is the label of a block with at least one
+  //   predecessor.
+  // - |message_.pred_to_id| contains a mapping from each of the predecessors of
+  //   the block to an id that is available at the end of the predecessor.
+  // - All the ids corresponding to a predecessor in |message_.pred_to_id|:
+  //    - have been recorded as synonymous and all have the same type.
+  //      TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3726): if a
+  //       predecessor is a dead block, any id of the right type could be used,
+  //       even if it is not synonym with the others.
+  //    - have one of the following types: Bool, Integer, Float, Vector, Matrix,
+  //      Array, Struct. Pointer types are also allowed if the VariablePointers
+  //      capability is enabled and the storage class is Workgroup or
+  //      StorageBuffer.
+  // - |message_.fresh_id| is a fresh id.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Given a block with n predecessors, with n >= 1, and n corresponding
+  // synonymous ids of the same type, each available to use at the end of the
+  // corresponding predecessor, adds an OpPhi instruction at the beginning of
+  // the block of the form:
+  //   %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
+  // This instruction is then marked as synonymous with the ids.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  // Returns true if |type_id| is the id of a type in the module, which is one
+  // of the following: Bool, Integer, Float, Vector, Matrix, Array, Struct.
+  // Pointer types are also allowed if the VariablePointers capability is
+  // enabled and the storage class is Workgroup or StorageBuffer.
+  static bool CheckTypeIsAllowed(opt::IRContext* ir_context, uint32_t type_id);
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddOpPhiSynonym message_;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_

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

@@ -14,12 +14,43 @@
 
 #include "source/fuzz/transformation_context.h"
 
+#include <cassert>
+
+#include "source/util/make_unique.h"
+
 namespace spvtools {
 namespace fuzz {
+namespace {
+
+// An overflow id source that should never be used: its methods assert false.
+// This is the right id source for use during fuzzing, when overflow ids should
+// never be required.
+class NullOverflowIdSource : public OverflowIdSource {
+  bool HasOverflowIds() const override {
+    assert(false && "Bad attempt to query whether overflow ids are available.");
+    return false;
+  }
+
+  uint32_t GetNextOverflowId() override {
+    assert(false && "Bad attempt to request an overflow id.");
+    return 0;
+  }
+};
+
+}  // namespace
 
 TransformationContext::TransformationContext(
     FactManager* fact_manager, spv_validator_options validator_options)
-    : fact_manager_(fact_manager), validator_options_(validator_options) {}
+    : fact_manager_(fact_manager),
+      validator_options_(validator_options),
+      overflow_id_source_(MakeUnique<NullOverflowIdSource>()) {}
+
+TransformationContext::TransformationContext(
+    FactManager* fact_manager, spv_validator_options validator_options,
+    std::unique_ptr<OverflowIdSource> overflow_id_source)
+    : fact_manager_(fact_manager),
+      validator_options_(validator_options),
+      overflow_id_source_(std::move(overflow_id_source)) {}
 
 TransformationContext::~TransformationContext() = default;
 

+ 20 - 2
3rdparty/spirv-tools/source/fuzz/transformation_context.h

@@ -15,7 +15,10 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
 
-#include "source/fuzz/fact_manager.h"
+#include <memory>
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+#include "source/fuzz/overflow_id_source.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -26,16 +29,29 @@ namespace fuzz {
 class TransformationContext {
  public:
   // Constructs a transformation context with a given fact manager and validator
-  // options.
+  // options.  Overflow ids are not available from a transformation context
+  // constructed in this way.
   TransformationContext(FactManager* fact_manager,
                         spv_validator_options validator_options);
 
+  // Constructs a transformation context with a given fact manager, validator
+  // options and overflow id source.
+  TransformationContext(FactManager* fact_manager,
+                        spv_validator_options validator_options,
+                        std::unique_ptr<OverflowIdSource> overflow_id_source);
+
   ~TransformationContext();
 
   FactManager* GetFactManager() { return fact_manager_; }
 
   const FactManager* GetFactManager() const { return fact_manager_; }
 
+  OverflowIdSource* GetOverflowIdSource() { return overflow_id_source_.get(); }
+
+  const OverflowIdSource* GetOverflowIdSource() const {
+    return overflow_id_source_.get();
+  }
+
   spv_validator_options GetValidatorOptions() const {
     return validator_options_;
   }
@@ -48,6 +64,8 @@ class TransformationContext {
   // Options to control validation when deciding whether transformations can be
   // applied.
   spv_validator_options validator_options_;
+
+  std::unique_ptr<OverflowIdSource> overflow_id_source_;
 };
 
 }  // namespace fuzz

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

@@ -0,0 +1,281 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_inline_function.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationInlineFunction::TransformationInlineFunction(
+    const spvtools::fuzz::protobufs::TransformationInlineFunction& message)
+    : message_(message) {}
+
+TransformationInlineFunction::TransformationInlineFunction(
+    uint32_t function_call_id,
+    const std::map<uint32_t, uint32_t>& result_id_map) {
+  message_.set_function_call_id(function_call_id);
+  *message_.mutable_result_id_map() =
+      fuzzerutil::MapToRepeatedUInt32Pair(result_id_map);
+}
+
+bool TransformationInlineFunction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The values in the |message_.result_id_map| must be all fresh and all
+  // distinct.
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+  std::set<uint32_t> ids_used_by_this_transformation;
+  for (auto& pair : result_id_map) {
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+            pair.second, ir_context, &ids_used_by_this_transformation)) {
+      return false;
+    }
+  }
+
+  // |function_call_instruction| must be suitable for inlining.
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  if (!IsSuitableForInlining(ir_context, function_call_instruction)) {
+    return false;
+  }
+
+  // |function_call_instruction| must be the penultimate instruction in its
+  // block and its block termination instruction must be an OpBranch. This
+  // avoids the case where the penultimate instruction is an OpLoopMerge, which
+  // would make the back-edge block not branch to the loop header.
+  auto* function_call_instruction_block =
+      ir_context->get_instr_block(function_call_instruction);
+  if (function_call_instruction !=
+          &*--function_call_instruction_block->tail() ||
+      function_call_instruction_block->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  for (auto& block : *called_function) {
+    // Since the entry block label will not be inlined, only the remaining
+    // labels must have a corresponding value in the map.
+    if (&block != &*called_function->entry() &&
+        !result_id_map.count(block.GetLabel()->result_id())) {
+      return false;
+    }
+
+    // |result_id_map| must have an entry for every result id in the called
+    // function.
+    for (auto& instruction : block) {
+      // If |instruction| has result id, then it must have a mapped id in
+      // |result_id_map|.
+      if (instruction.HasResultId() &&
+          !result_id_map.count(instruction.result_id())) {
+        return false;
+      }
+    }
+  }
+
+  // |result_id_map| must not contain an entry for any parameter of the function
+  // that is being inlined.
+  bool found_entry_for_parameter = false;
+  called_function->ForEachParam(
+      [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) {
+        if (result_id_map.count(param->result_id())) {
+          found_entry_for_parameter = true;
+        }
+      });
+  return !found_entry_for_parameter;
+}
+
+void TransformationInlineFunction::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  auto* caller_function =
+      ir_context->get_instr_block(function_call_instruction)->GetParent();
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+  auto* successor_block = ir_context->cfg()->block(
+      ir_context->get_instr_block(function_call_instruction)
+          ->terminator()
+          ->GetSingleWordInOperand(0));
+
+  // Inline the |called_function| entry block.
+  for (auto& entry_block_instruction : *called_function->entry()) {
+    opt::Instruction* inlined_instruction = nullptr;
+
+    if (entry_block_instruction.opcode() == SpvOpVariable) {
+      // All OpVariable instructions in a function must be in the first block
+      // in the function.
+      inlined_instruction = caller_function->begin()->begin()->InsertBefore(
+          MakeUnique<opt::Instruction>(entry_block_instruction));
+    } else {
+      inlined_instruction = function_call_instruction->InsertBefore(
+          MakeUnique<opt::Instruction>(entry_block_instruction));
+    }
+
+    AdaptInlinedInstruction(ir_context, inlined_instruction);
+  }
+
+  // Inline the |called_function| non-entry blocks.
+  for (auto& block : *called_function) {
+    if (&block == &*called_function->entry()) {
+      continue;
+    }
+
+    auto* cloned_block = block.Clone(ir_context);
+    cloned_block = caller_function->InsertBasicBlockBefore(
+        std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block);
+    cloned_block->SetParent(caller_function);
+    cloned_block->GetLabel()->SetResultId(
+        result_id_map.at(cloned_block->GetLabel()->result_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    cloned_block->GetLabel()->result_id());
+
+    for (auto& inlined_instruction : *cloned_block) {
+      AdaptInlinedInstruction(ir_context, &inlined_instruction);
+    }
+  }
+
+  // Removes the function call instruction and its block termination instruction
+  // from |caller_function|.
+  ir_context->KillInst(
+      ir_context->get_instr_block(function_call_instruction)->terminator());
+  ir_context->KillInst(function_call_instruction);
+
+  // Since the SPIR-V module has changed, no analyses must be validated.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationInlineFunction::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_inline_function() = message_;
+  return result;
+}
+
+bool TransformationInlineFunction::IsSuitableForInlining(
+    opt::IRContext* ir_context, opt::Instruction* function_call_instruction) {
+  // |function_call_instruction| must be defined and must be an OpFunctionCall
+  // instruction.
+  if (!function_call_instruction ||
+      function_call_instruction->opcode() != SpvOpFunctionCall) {
+    return false;
+  }
+
+  // If |function_call_instruction| return type is void, then
+  // |function_call_instruction| must not have uses.
+  if (ir_context->get_type_mgr()
+          ->GetType(function_call_instruction->type_id())
+          ->AsVoid() &&
+      ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) {
+    return false;
+  }
+
+  // |called_function| must not have an early return.
+  auto called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  if (called_function->HasEarlyReturn()) {
+    return false;
+  }
+
+  // |called_function| must not use OpKill or OpUnreachable.
+  if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationInlineFunction::AdaptInlinedInstruction(
+    opt::IRContext* ir_context,
+    opt::Instruction* instruction_to_be_inlined) const {
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+
+  // Replaces the operand ids with their mapped result ids.
+  instruction_to_be_inlined->ForEachInId([called_function,
+                                          function_call_instruction,
+                                          &result_id_map](uint32_t* id) {
+    // If |id| is mapped, then set it to its mapped value.
+    if (result_id_map.count(*id)) {
+      *id = result_id_map.at(*id);
+      return;
+    }
+
+    uint32_t parameter_index = 0;
+    called_function->ForEachParam(
+        [id, function_call_instruction,
+         &parameter_index](opt::Instruction* parameter_instruction) {
+          // If the id is a function parameter, then set it to the
+          // parameter value passed in the function call instruction.
+          if (*id == parameter_instruction->result_id()) {
+            // We do + 1 because the first in-operand for OpFunctionCall is
+            // the function id that is being called.
+            *id = function_call_instruction->GetSingleWordInOperand(
+                parameter_index + 1);
+          }
+          parameter_index++;
+        });
+  });
+
+  // If |instruction_to_be_inlined| has result id, then set it to its mapped
+  // value.
+  if (instruction_to_be_inlined->HasResultId()) {
+    assert(result_id_map.count(instruction_to_be_inlined->result_id()) &&
+           "Result id must be mapped to a fresh id.");
+    instruction_to_be_inlined->SetResultId(
+        result_id_map.at(instruction_to_be_inlined->result_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    instruction_to_be_inlined->result_id());
+  }
+
+  // The return instruction will be changed into an OpBranch to the basic
+  // block that follows the block containing the function call.
+  if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) {
+    uint32_t successor_block_id =
+        ir_context->get_instr_block(function_call_instruction)
+            ->terminator()
+            ->GetSingleWordInOperand(0);
+    switch (instruction_to_be_inlined->opcode()) {
+      case SpvOpReturn:
+        instruction_to_be_inlined->AddOperand(
+            {SPV_OPERAND_TYPE_ID, {successor_block_id}});
+        break;
+      case SpvOpReturnValue: {
+        instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpCopyObject, function_call_instruction->type_id(),
+            function_call_instruction->result_id(),
+            opt::Instruction::OperandList(
+                {{SPV_OPERAND_TYPE_ID,
+                  {instruction_to_be_inlined->GetSingleWordOperand(0)}}})));
+        instruction_to_be_inlined->SetInOperand(0, {successor_block_id});
+        break;
+      }
+      default:
+        break;
+    }
+    instruction_to_be_inlined->SetOpcode(SpvOpBranch);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools

+ 75 - 0
3rdparty/spirv-tools/source/fuzz/transformation_inline_function.h

@@ -0,0 +1,75 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationInlineFunction : public Transformation {
+ public:
+  explicit TransformationInlineFunction(
+      const protobufs::TransformationInlineFunction& message);
+
+  TransformationInlineFunction(
+      uint32_t function_call_id,
+      const std::map<uint32_t, uint32_t>& result_id_map);
+
+  // - |message_.result_id_map| must map the instructions of the called function
+  //   to fresh ids.
+  // - |message_.function_call_id| must be an OpFunctionCall instruction.
+  //   It must not have an early return and must not use OpUnreachable or
+  //   OpKill. This is to guard against making the module invalid when the
+  //   caller is inside a continue construct.
+  //   TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3735):
+  //     Allow functions that use OpKill or OpUnreachable to be inlined if the
+  //     function call is not part of a continue construct.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the OpFunctionCall instruction, identified by
+  // |message_.function_call_id|, with a copy of the function's body.
+  // |message_.result_id_map| is used to provide fresh ids for duplicate
+  // instructions.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if |function_call_instruction| is defined, is an
+  // OpFunctionCall instruction, has no uses if its return type is void, has no
+  // early returns and has no uses of OpKill or OpUnreachable.
+  static bool IsSuitableForInlining(
+      opt::IRContext* ir_context, opt::Instruction* function_call_instruction);
+
+ private:
+  protobufs::TransformationInlineFunction message_;
+
+  // Inline |instruction_to_be_inlined| by setting its ids to the corresponding
+  // ids in |result_id_map|.
+  void AdaptInlinedInstruction(opt::IRContext* ir_context,
+                               opt::Instruction* instruction) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_

+ 27 - 6
3rdparty/spirv-tools/source/fuzz/transformation_outline_function.cpp

@@ -48,7 +48,8 @@ TransformationOutlineFunction::TransformationOutlineFunction(
 }
 
 bool TransformationOutlineFunction::IsApplicable(
-    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   std::set<uint32_t> ids_used_by_this_transformation;
 
   // The various new ids used by the transformation must be fresh and distinct.
@@ -234,8 +235,9 @@ bool TransformationOutlineFunction::IsApplicable(
       fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
   for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
     // There needs to be a corresponding fresh id to be used as a function
-    // parameter.
-    if (input_id_to_fresh_id_map.count(id) == 0) {
+    // parameter, or overflow ids need to be available.
+    if (input_id_to_fresh_id_map.count(id) == 0 &&
+        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
       return false;
     }
     // Furthermore, if the input id has pointer type it must be an OpVariable
@@ -263,8 +265,10 @@ bool TransformationOutlineFunction::IsApplicable(
   for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
     if (
         // ... there needs to be a corresponding fresh id that can hold the
-        // value for this id computed in the outlined function, and ...
-        output_id_to_fresh_id_map.count(id) == 0
+        // value for this id computed in the outlined function (or overflow ids
+        // must be available), and ...
+        (output_id_to_fresh_id_map.count(id) == 0 &&
+         !transformation_context.GetOverflowIdSource()->HasOverflowIds())
         // ... the output id must not have pointer type (to avoid creating a
         // struct with pointer members to pass data out of the outlined
         // function)
@@ -306,6 +310,23 @@ void TransformationOutlineFunction::Apply(
   auto output_id_to_fresh_id_map =
       fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
 
+  // Use overflow ids to augment these maps at any locations where fresh ids are
+  // required but not provided.
+  for (uint32_t id : region_input_ids) {
+    if (input_id_to_fresh_id_map.count(id) == 0) {
+      input_id_to_fresh_id_map.insert(
+          {id,
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+  }
+  for (uint32_t id : region_output_ids) {
+    if (output_id_to_fresh_id_map.count(id) == 0) {
+      output_id_to_fresh_id_map.insert(
+          {id,
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+  }
+
   UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
                                  output_id_to_fresh_id_map);
 
@@ -609,7 +630,7 @@ TransformationOutlineFunction::PrepareFunctionPrototype(
                 {function_type_id}}})));
 
   // Add one parameter to the function for each input id, using the fresh ids
-  // provided in |input_id_to_fresh_id_map|.
+  // provided in |input_id_to_fresh_id_map|, or overflow ids if needed.
   for (auto id : region_input_ids) {
     outlined_function->AddParameter(MakeUnique<opt::Instruction>(
         ir_context, SpvOpFunctionParameter,

+ 0 - 1
3rdparty/spirv-tools/source/fuzz/transformation_replace_constant_with_uniform.h

@@ -17,7 +17,6 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
 #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"

+ 38 - 11
3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp

@@ -21,6 +21,24 @@
 
 namespace spvtools {
 namespace opt {
+namespace {
+
+// Returns the remapped id of |id| from |result_id_mapping|. If the remapped
+// id does not exist, adds a new one to |result_id_mapping| and returns it.
+uint32_t GetRemappedId(
+    std::unordered_map<uint32_t, uint32_t>* result_id_mapping, uint32_t id) {
+  auto it = result_id_mapping->find(id);
+  if (it == result_id_mapping->end()) {
+    const uint32_t new_id =
+        static_cast<uint32_t>(result_id_mapping->size()) + 1;
+    const auto insertion_result = result_id_mapping->emplace(id, new_id);
+    it = insertion_result.first;
+    assert(insertion_result.second);
+  }
+  return it->second;
+}
+
+}  // namespace
 
 Pass::Status CompactIdsPass::Process() {
   bool modified = false;
@@ -34,18 +52,10 @@ Pass::Status CompactIdsPass::Process() {
           if (spvIsIdType(type)) {
             assert(operand->words.size() == 1);
             uint32_t& id = operand->words[0];
-            auto it = result_id_mapping.find(id);
-            if (it == result_id_mapping.end()) {
-              const uint32_t new_id =
-                  static_cast<uint32_t>(result_id_mapping.size()) + 1;
-              const auto insertion_result =
-                  result_id_mapping.emplace(id, new_id);
-              it = insertion_result.first;
-              assert(insertion_result.second);
-            }
-            if (id != it->second) {
+            uint32_t new_id = GetRemappedId(&result_id_mapping, id);
+            if (id != new_id) {
               modified = true;
-              id = it->second;
+              id = new_id;
               // Update data cached in the instruction object.
               if (type == SPV_OPERAND_TYPE_RESULT_ID) {
                 inst->SetResultId(id);
@@ -56,6 +66,23 @@ Pass::Status CompactIdsPass::Process() {
           }
           ++operand;
         }
+
+        uint32_t scope_id = inst->GetDebugScope().GetLexicalScope();
+        if (scope_id != kNoDebugScope) {
+          uint32_t new_id = GetRemappedId(&result_id_mapping, scope_id);
+          if (scope_id != new_id) {
+            inst->UpdateLexicalScope(new_id);
+            modified = true;
+          }
+        }
+        uint32_t inlinedat_id = inst->GetDebugInlinedAt();
+        if (inlinedat_id != kNoInlinedAt) {
+          uint32_t new_id = GetRemappedId(&result_id_mapping, inlinedat_id);
+          if (inlinedat_id != new_id) {
+            inst->UpdateDebugInlinedAt(new_id);
+            modified = true;
+          }
+        }
       },
       true);
 

+ 7 - 0
3rdparty/spirv-tools/source/opt/debug_info_manager.cpp

@@ -99,6 +99,13 @@ void DebugInfoManager::RegisterDbgFunction(Instruction* inst) {
   assert(inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction &&
          "inst is not a DebugFunction");
   auto fn_id = inst->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex);
+  // Do not register function that has been optimized away
+  auto fn_inst = GetDbgInst(fn_id);
+  if (fn_inst != nullptr) {
+    assert(GetDbgInst(fn_id)->GetOpenCL100DebugOpcode() ==
+           OpenCLDebugInfo100DebugInfoNone);
+    return;
+  }
   assert(
       fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
       "Register DebugFunction for a function that already has DebugFunction");

+ 8 - 0
3rdparty/spirv-tools/source/opt/inline_pass.cpp

@@ -619,6 +619,14 @@ bool InlinePass::GenInlineCode(
     assert(resId != 0);
     AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr,
             call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope());
+  } else {
+    // Even though it is very unlikely, it is possible that the result id of
+    // the void-function call is used, so we need to generate an instruction
+    // with that result id.
+    std::unique_ptr<Instruction> undef_inst(
+        new Instruction(context(), SpvOpUndef, call_inst_itr->type_id(),
+                        call_inst_itr->result_id(), {}));
+    context()->AddGlobalValue(std::move(undef_inst));
   }
 
   // Move instructions of original caller block after call instruction.

+ 4 - 2
3rdparty/spirv-tools/source/opt/instruction.cpp

@@ -921,8 +921,10 @@ void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id,
       static_cast<uint32_t>(dbg_opcode),
   };
   binary->insert(binary->end(), operands.begin(), operands.end());
-  if (GetLexicalScope() != kNoDebugScope) binary->push_back(GetLexicalScope());
-  if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt());
+  if (GetLexicalScope() != kNoDebugScope) {
+    binary->push_back(GetLexicalScope());
+    if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt());
+  }
 }
 
 }  // namespace opt

+ 9 - 0
3rdparty/spirv-tools/source/opt/instruction.h

@@ -305,6 +305,8 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> {
   inline uint32_t GetDebugInlinedAt() const {
     return dbg_scope_.GetInlinedAt();
   }
+  // Updates lexical scope of DebugScope and OpLine.
+  inline void UpdateLexicalScope(uint32_t scope);
   // Updates OpLine and DebugScope based on the information of |from|.
   inline void UpdateDebugInfoFrom(const Instruction* from);
   // Remove the |index|-th operand
@@ -668,6 +670,13 @@ inline void Instruction::SetDebugScope(const DebugScope& scope) {
   }
 }
 
+inline void Instruction::UpdateLexicalScope(uint32_t scope) {
+  dbg_scope_.SetLexicalScope(scope);
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_.SetLexicalScope(scope);
+  }
+}
+
 inline void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) {
   dbg_scope_.SetInlinedAt(new_inlined_at);
   for (auto& i : dbg_line_insts_) {

+ 3 - 5
3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp

@@ -624,11 +624,9 @@ Pass::Status SSARewritePass::Process() {
   for (auto& fn : *get_module()) {
     status =
         CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn));
-    if (status == Status::SuccessWithChange) {
-      // Kill DebugDeclares for target variables.
-      for (auto var_id : seen_target_vars_) {
-        context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
-      }
+    // Kill DebugDeclares for target variables.
+    for (auto var_id : seen_target_vars_) {
+      context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
     }
     if (status == Status::Failure) {
       break;

+ 13 - 0
3rdparty/spirv-tools/source/opt/struct_cfg_analysis.cpp

@@ -150,6 +150,19 @@ uint32_t StructuredCFGAnalysis::LoopContinueBlock(uint32_t bb_id) {
   return merge_inst->GetSingleWordInOperand(kContinueNodeIndex);
 }
 
+uint32_t StructuredCFGAnalysis::LoopNestingDepth(uint32_t bb_id) {
+  uint32_t result = 0;
+
+  // Find the merge block of the current loop as long as the block is inside a
+  // loop, exiting a loop for each iteration.
+  for (uint32_t merge_block_id = LoopMergeBlock(bb_id); merge_block_id != 0;
+       merge_block_id = LoopMergeBlock(merge_block_id)) {
+    result++;
+  }
+
+  return result;
+}
+
 uint32_t StructuredCFGAnalysis::SwitchMergeBlock(uint32_t bb_id) {
   uint32_t header_id = ContainingSwitch(bb_id);
   if (header_id == 0) {

+ 7 - 0
3rdparty/spirv-tools/source/opt/struct_cfg_analysis.h

@@ -74,6 +74,13 @@ class StructuredCFGAnalysis {
   // construct.
   uint32_t LoopContinueBlock(uint32_t bb_id);
 
+  // Returns the loop nesting depth of |bb_id| within its function, i.e. the
+  // number of loop constructs in which |bb_id| is contained. As per other
+  // functions in StructuredCFGAnalysis, a loop header is not regarded as being
+  // part of the loop that it heads, so that e.g. the nesting depth of an
+  // outer-most loop header is 0.
+  uint32_t LoopNestingDepth(uint32_t bb_id);
+
   // Returns the id of the header of the innermost switch construct
   // that contains |bb_id| as long as there is no intervening loop.  Returns |0|
   // if no such construct exists.