spirv-fuzz: Efficiency improvements to fuzzer pass (#4188)

FuzzerPassConstructComposites is adapted to use AvailableInstructions
to manage available instructions, and to use zero constants when
trying to construct a composite for which not all fields can otherwise
be constructed. The change uncovered some cases where we create
structs and arrays with struct fields or components that are
block-decorated; these possibilities have been eliminated.
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.cpp b/source/fuzz/fuzzer_pass_add_composite_types.cpp
index a59cbfa..3dfbd69 100644
--- a/source/fuzz/fuzzer_pass_add_composite_types.cpp
+++ b/source/fuzz/fuzzer_pass_add_composite_types.cpp
@@ -123,7 +123,9 @@
         break;
       case SpvOpTypeStruct: {
         if (!fuzzerutil::MembersHaveBuiltInDecoration(GetIRContext(),
-                                                      inst.result_id())) {
+                                                      inst.result_id()) &&
+            !fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
+                                                         inst.result_id())) {
           candidates.push_back(inst.result_id());
         }
       } break;
diff --git a/source/fuzz/fuzzer_pass_construct_composites.cpp b/source/fuzz/fuzzer_pass_construct_composites.cpp
index dc02424..1a174cf 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.cpp
+++ b/source/fuzz/fuzzer_pass_construct_composites.cpp
@@ -16,6 +16,7 @@
 
 #include <memory>
 
+#include "source/fuzz/available_instructions.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_composite_construct.h"
 
@@ -42,12 +43,40 @@
     }
   }
 
+  if (composite_type_ids.empty()) {
+    // There are no composite types, so this fuzzer pass cannot do anything.
+    return;
+  }
+
+  AvailableInstructions available_composite_constituents(
+      GetIRContext(),
+      [this](opt::IRContext* ir_context, opt::Instruction* inst) -> bool {
+        if (!inst->result_id() || !inst->type_id()) {
+          return false;
+        }
+
+        // If the id is irrelevant, we can use it since it will not
+        // participate in DataSynonym fact. Otherwise, we should be able
+        // to produce a synonym out of the id.
+        return GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+                   inst->result_id()) ||
+               fuzzerutil::CanMakeSynonymOf(ir_context,
+                                            *GetTransformationContext(), inst);
+      });
+
   ForEachInstructionWithInstructionDescriptor(
-      [this, &composite_type_ids](
-          opt::Function* function, opt::BasicBlock* block,
+      [this, &available_composite_constituents, &composite_type_ids](
+          opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
           opt::BasicBlock::iterator inst_it,
           const protobufs::InstructionDescriptor& instruction_descriptor)
           -> void {
+        // Randomly decide whether to try inserting a composite construction
+        // here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfConstructingComposite())) {
+          return;
+        }
+
         // Check whether it is legitimate to insert a composite construction
         // before the instruction.
         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
@@ -55,36 +84,21 @@
           return;
         }
 
-        // Randomly decide whether to try inserting an object copy here.
-        if (!GetFuzzerContext()->ChoosePercentage(
-                GetFuzzerContext()->GetChanceOfConstructingComposite())) {
-          return;
-        }
-
         // For each instruction that is available at this program point (i.e. an
         // instruction that is global or whose definition strictly dominates the
         // program point) and suitable for making a synonym of, associate it
         // with the id of its result type.
         TypeIdToInstructions type_id_to_available_instructions;
-        auto available_instructions = FindAvailableInstructions(
-            function, block, inst_it,
-            [this](opt::IRContext* ir_context, opt::Instruction* inst) {
-              if (!inst->result_id() || !inst->type_id()) {
-                return false;
-              }
-
-              // If the id is irrelevant, we can use it since it will not
-              // participate in DataSynonym fact. Otherwise, we should be able
-              // to produce a synonym out of the id.
-              return GetTransformationContext()
-                         ->GetFactManager()
-                         ->IdIsIrrelevant(inst->result_id()) ||
-                     fuzzerutil::CanMakeSynonymOf(
-                         ir_context, *GetTransformationContext(), inst);
-            });
-        for (auto instruction : available_instructions) {
-          RecordAvailableInstruction(instruction,
-                                     &type_id_to_available_instructions);
+        auto available_instructions =
+            available_composite_constituents.GetAvailableBeforeInstruction(
+                &*inst_it);
+        for (uint32_t available_instruction_index = 0;
+             available_instruction_index < available_instructions.size();
+             available_instruction_index++) {
+          opt::Instruction* inst =
+              available_instructions[available_instruction_index];
+          type_id_to_available_instructions[inst->type_id()].push_back(
+              inst->result_id());
         }
 
         // At this point, |composite_type_ids| captures all the composite types
@@ -92,68 +106,41 @@
         // captures all the available result ids we might use, organized by
         // type.
 
-        // Now we try to find a composite that we can construct.  We might not
-        // manage, if there is a paucity of available ingredients in the module
-        // (e.g. if our only available composite was a boolean vector and we had
-        // no instructions generating boolean result types available).
-        //
-        // If we succeed, |chosen_composite_type| will end up being non-zero,
-        // and |constructor_arguments| will end up giving us result ids suitable
-        // for constructing a composite of that type.  Otherwise these variables
-        // will remain 0 and null respectively.
-        uint32_t chosen_composite_type = 0;
+        // Now we choose a composite type to construct, building it from
+        // available constituent components and using zero constants if suitable
+        // components are not available.
+
         std::vector<uint32_t> constructor_arguments;
+        uint32_t chosen_composite_type =
+            composite_type_ids[GetFuzzerContext()->RandomIndex(
+                composite_type_ids)];
 
-        // Initially, all composite type ids are available for us to try.  Keep
-        // trying until we run out of options.
-        auto composites_to_try_constructing = composite_type_ids;
-        while (!composites_to_try_constructing.empty()) {
-          // Remove a composite type from the composite types left for us to
-          // try.
-          auto next_composite_to_try_constructing =
-              GetFuzzerContext()->RemoveAtRandomIndex(
-                  &composites_to_try_constructing);
-
-          // Now try to construct a composite of this type, using an appropriate
-          // helper method depending on the kind of composite type.
-          auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
-              next_composite_to_try_constructing);
-          switch (composite_type_inst->opcode()) {
-            case SpvOpTypeArray:
-              constructor_arguments = FindComponentsToConstructArray(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            case SpvOpTypeMatrix:
-              constructor_arguments = FindComponentsToConstructMatrix(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            case SpvOpTypeStruct:
-              constructor_arguments = FindComponentsToConstructStruct(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            case SpvOpTypeVector:
-              constructor_arguments = FindComponentsToConstructVector(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            default:
-              assert(false &&
-                     "The space of possible composite types should be covered "
-                     "by the above cases.");
-              break;
-          }
-          if (!constructor_arguments.empty()) {
-            // We succeeded!  Note the composite type we finally settled on, and
-            // exit from the loop.
-            chosen_composite_type = next_composite_to_try_constructing;
+        // Construct a composite of this type, using an appropriate helper
+        // method depending on the kind of composite type.
+        auto composite_type_inst =
+            GetIRContext()->get_def_use_mgr()->GetDef(chosen_composite_type);
+        switch (composite_type_inst->opcode()) {
+          case SpvOpTypeArray:
+            constructor_arguments = FindComponentsToConstructArray(
+                *composite_type_inst, type_id_to_available_instructions);
             break;
-          }
-        }
-
-        if (!chosen_composite_type) {
-          // We did not manage to make a composite; return 0 to indicate that no
-          // instructions were added.
-          assert(constructor_arguments.empty());
-          return;
+          case SpvOpTypeMatrix:
+            constructor_arguments = FindComponentsToConstructMatrix(
+                *composite_type_inst, type_id_to_available_instructions);
+            break;
+          case SpvOpTypeStruct:
+            constructor_arguments = FindComponentsToConstructStruct(
+                *composite_type_inst, type_id_to_available_instructions);
+            break;
+          case SpvOpTypeVector:
+            constructor_arguments = FindComponentsToConstructVector(
+                *composite_type_inst, type_id_to_available_instructions);
+            break;
+          default:
+            assert(false &&
+                   "The space of possible composite types should be covered "
+                   "by the above cases.");
+            break;
         }
         assert(!constructor_arguments.empty());
 
@@ -164,15 +151,6 @@
       });
 }
 
-void FuzzerPassConstructComposites::RecordAvailableInstruction(
-    opt::Instruction* inst,
-    TypeIdToInstructions* type_id_to_available_instructions) {
-  if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
-    (*type_id_to_available_instructions)[inst->type_id()] = {};
-  }
-  type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
-}
-
 std::vector<uint32_t>
 FuzzerPassConstructComposites::FindComponentsToConstructArray(
     const opt::Instruction& array_type_instruction,
@@ -188,13 +166,6 @@
   auto available_instructions =
       type_id_to_available_instructions.find(element_type_id);
 
-  if (available_instructions == type_id_to_available_instructions.cend()) {
-    // If there are not any instructions available that compute the element type
-    // of the array then we are not in a position to construct a composite with
-    // this array type.
-    return {};
-  }
-
   uint32_t array_length =
       GetIRContext()
           ->get_def_use_mgr()
@@ -203,10 +174,14 @@
 
   std::vector<uint32_t> result;
   for (uint32_t index = 0; index < array_length; index++) {
-    result.push_back(available_instructions
-                         ->second[GetFuzzerContext()->RandomIndex(
-                             available_instructions->second)]
-                         ->result_id());
+    if (available_instructions == type_id_to_available_instructions.cend()) {
+      // No suitable instructions are available, so use a zero constant
+      result.push_back(FindOrCreateZeroConstant(element_type_id, true));
+    } else {
+      result.push_back(
+          available_instructions->second[GetFuzzerContext()->RandomIndex(
+              available_instructions->second)]);
+    }
   }
   return result;
 }
@@ -226,19 +201,17 @@
   auto available_instructions =
       type_id_to_available_instructions.find(element_type_id);
 
-  if (available_instructions == type_id_to_available_instructions.cend()) {
-    // If there are not any instructions available that compute the element type
-    // of the matrix then we are not in a position to construct a composite with
-    // this matrix type.
-    return {};
-  }
   std::vector<uint32_t> result;
   for (uint32_t index = 0;
        index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
-    result.push_back(available_instructions
-                         ->second[GetFuzzerContext()->RandomIndex(
-                             available_instructions->second)]
-                         ->result_id());
+    if (available_instructions == type_id_to_available_instructions.cend()) {
+      // No suitable components are available, so use a zero constant.
+      result.push_back(FindOrCreateZeroConstant(element_type_id, true));
+    } else {
+      result.push_back(
+          available_instructions->second[GetFuzzerContext()->RandomIndex(
+              available_instructions->second)]);
+    }
   }
   return result;
 }
@@ -261,14 +234,14 @@
     auto available_instructions =
         type_id_to_available_instructions.find(element_type_id);
     if (available_instructions == type_id_to_available_instructions.cend()) {
-      // If there are no such instructions, we cannot construct a composite of
-      // this struct type.
-      return {};
+      // No suitable component is available for this element type, so use a zero
+      // constant.
+      result.push_back(FindOrCreateZeroConstant(element_type_id, true));
+    } else {
+      result.push_back(
+          available_instructions->second[GetFuzzerContext()->RandomIndex(
+              available_instructions->second)]);
     }
-    result.push_back(available_instructions
-                         ->second[GetFuzzerContext()->RandomIndex(
-                             available_instructions->second)]
-                         ->result_id());
   }
   return result;
 }
@@ -323,12 +296,13 @@
   // (otherwise there will not be space left for a vec3).
 
   uint32_t vector_slots_used = 0;
-  // The instructions we will use to construct the vector, in no particular
-  // order at this stage.
-  std::vector<opt::Instruction*> instructions_to_use;
+
+  // The instructions result ids we will use to construct the vector, in no
+  // particular order at this stage.
+  std::vector<uint32_t> result;
 
   while (vector_slots_used < element_count) {
-    std::vector<opt::Instruction*> instructions_to_choose_from;
+    std::vector<uint32_t> instructions_to_choose_from;
     for (auto& entry : smaller_vector_type_id_to_width) {
       if (entry.second >
           std::min(element_count - 1, element_count - vector_slots_used)) {
@@ -343,19 +317,16 @@
                                          available_instructions->second.begin(),
                                          available_instructions->second.end());
     }
-    if (instructions_to_choose_from.empty()) {
-      // We may get unlucky and find that there are not any instructions to
-      // choose from.  In this case we give up constructing a composite of this
-      // vector type.  It might be that we could construct the composite in
-      // another manner, so we could opt to retry a few times here, but it is
-      // simpler to just give up on the basis that this will not happen
-      // frequently.
-      return {};
-    }
-    auto instruction_to_use =
-        instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
-            instructions_to_choose_from)];
-    instructions_to_use.push_back(instruction_to_use);
+    // If there are no instructions to choose from then use a zero constant,
+    // otherwise select one of the instructions at random.
+    uint32_t id_of_instruction_to_use =
+        instructions_to_choose_from.empty()
+            ? FindOrCreateZeroConstant(element_type_id, true)
+            : instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
+                  instructions_to_choose_from)];
+    opt::Instruction* instruction_to_use =
+        GetIRContext()->get_def_use_mgr()->GetDef(id_of_instruction_to_use);
+    result.push_back(instruction_to_use->result_id());
     auto chosen_type =
         GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
     if (chosen_type->AsVector()) {
@@ -371,14 +342,7 @@
   }
   assert(vector_slots_used == element_count);
 
-  std::vector<uint32_t> result;
-  std::vector<uint32_t> operands;
-  while (!instructions_to_use.empty()) {
-    auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
-    result.push_back(instructions_to_use[index]->result_id());
-    instructions_to_use.erase(instructions_to_use.begin() + index);
-  }
-  assert(result.size() > 1);
+  GetFuzzerContext()->Shuffle(&result);
   return result;
 }
 
diff --git a/source/fuzz/fuzzer_pass_construct_composites.h b/source/fuzz/fuzzer_pass_construct_composites.h
index cc5f98b..333ac93 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.h
+++ b/source/fuzz/fuzzer_pass_construct_composites.h
@@ -15,7 +15,7 @@
 #ifndef SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
 #define SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
 
-#include <map>
+#include <unordered_map>
 #include <vector>
 
 #include "source/fuzz/fuzzer_pass.h"
@@ -34,18 +34,9 @@
   void Apply() override;
 
  private:
-  // Used to map a type id to relevant instructions whose result type matches
-  // the type id.
-  typedef std::map<uint32_t, std::vector<opt::Instruction*>>
-      TypeIdToInstructions;
-
-  // Considers all instructions that are available at |inst| - instructions
-  // whose results could be packed into a composite - and updates
-  // |type_id_to_available_instructions| so that each such instruction is
-  // associated with its the id of its result type.
-  void RecordAvailableInstruction(
-      opt::Instruction* inst,
-      TypeIdToInstructions* type_id_to_available_instructions);
+  // Used to map a type id to the ids of relevant instructions of the type.
+  using TypeIdToInstructions =
+      std::unordered_map<uint32_t, std::vector<uint32_t>>;
 
   // Requires that |array_type_instruction| has opcode OpTypeArray.
   // Attempts to find suitable instruction result ids from the values of
diff --git a/source/fuzz/transformation_add_type_array.cpp b/source/fuzz/transformation_add_type_array.cpp
index 87b4447..45bc8df 100644
--- a/source/fuzz/transformation_add_type_array.cpp
+++ b/source/fuzz/transformation_add_type_array.cpp
@@ -39,9 +39,11 @@
   }
   auto element_type =
       ir_context->get_type_mgr()->GetType(message_.element_type_id());
-  if (!element_type || element_type->AsFunction()) {
-    // The element type id either does not refer to a type, or refers to a
-    // function type; both are illegal.
+  if (!element_type || element_type->AsFunction() ||
+      fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context,
+                                                  message_.element_type_id())) {
+    // The element type id either does not refer to a type, refers to a function
+    // type, or refers to a block-decorated struct. These cases are all illegal.
     return false;
   }
   auto constant =
diff --git a/source/fuzz/transformation_add_type_array.h b/source/fuzz/transformation_add_type_array.h
index 4767fb8..3162497 100644
--- a/source/fuzz/transformation_add_type_array.h
+++ b/source/fuzz/transformation_add_type_array.h
@@ -33,8 +33,10 @@
 
   // - |message_.fresh_id| must be fresh
   // - |message_.element_type_id| must be the id of a non-function type
+  // - |message_.member_type_id| must not be the result id of an OpTypeStruct
+  //   instruction that has the Block or BufferBlock decoration
   // - |message_.size_id| must be the id of a 32-bit integer constant that is
-  //   positive when interpreted as signed.
+  //   positive when interpreted as signed
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
diff --git a/source/fuzz/transformation_add_type_struct.cpp b/source/fuzz/transformation_add_type_struct.cpp
index 3d5e15a..d7f0711 100644
--- a/source/fuzz/transformation_add_type_struct.cpp
+++ b/source/fuzz/transformation_add_type_struct.cpp
@@ -39,9 +39,11 @@
   }
   for (auto member_type : message_.member_type_id()) {
     auto type = ir_context->get_type_mgr()->GetType(member_type);
-    if (!type || type->AsFunction()) {
-      // The member type id either does not refer to a type, or refers to a
-      // function type; both are illegal.
+    if (!type || type->AsFunction() ||
+        fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context, member_type)) {
+      // The member type id either does not refer to a type, refers to a
+      // function type, or refers to a block-decorated struct. These cases are
+      // all illegal.
       return false;
     }
 
diff --git a/source/fuzz/transformation_add_type_struct.h b/source/fuzz/transformation_add_type_struct.h
index ea30d9b..6a8dce7 100644
--- a/source/fuzz/transformation_add_type_struct.h
+++ b/source/fuzz/transformation_add_type_struct.h
@@ -37,7 +37,9 @@
   // - |message_.member_type_id| must be a sequence of non-function type ids
   // - |message_.member_type_id| may not contain a result id of an OpTypeStruct
   //   instruction with BuiltIn members (i.e. members of the struct are
-  //   decorated via OpMemberDecorate with BuiltIn decoration).
+  //   decorated via OpMemberDecorate with BuiltIn decoration)
+  // - |message_.member_type_id| may not contain a result id of an OpTypeStruct
+  //   instruction that has the Block or BufferBlock decoration
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;