Employ the "swarm testing" idea in spirv-fuzz (#2890)

This change to spirv-fuzz uses ideas from "Swarm Testing" (Groce et al. 2012), so that a random subset of fuzzer passes are enabled. These passes are then applied repeatedly in a randomized fashion, with the aggression with which they are applied being randomly chosen per pass.

There is plenty of scope for refining the probabilities introduce in this change; this is just meant to be a reasonable first effort.
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 3b03493..8e36a32 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -15,6 +15,7 @@
 #include "source/fuzz/fuzzer.h"
 
 #include <cassert>
+#include <memory>
 #include <sstream>
 
 #include "source/fuzz/fact_manager.h"
@@ -38,8 +39,25 @@
 
 namespace {
 const uint32_t kIdBoundGap = 100;
+
+const uint32_t kTransformationLimit = 500;
+
+const uint32_t kChanceOfApplyingAnotherPass = 85;
+
+template <typename T>
+void MaybeAddPass(
+    std::vector<std::unique_ptr<FuzzerPass>>* passes,
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformation_sequence_out) {
+  if (fuzzer_context->ChooseEven()) {
+    passes->push_back(MakeUnique<T>(ir_context, fact_manager, fuzzer_context,
+                                    transformation_sequence_out));
+  }
 }
 
+}  // namespace
+
 struct Fuzzer::Impl {
   explicit Impl(spv_target_env env) : target_env(env) {}
 
@@ -109,29 +127,40 @@
       .Apply();
 
   // Apply some semantics-preserving passes.
-  FuzzerPassCopyObjects(ir_context.get(), &fact_manager, &fuzzer_context,
-                        transformation_sequence_out)
-      .Apply();
-  FuzzerPassApplyIdSynonyms(ir_context.get(), &fact_manager, &fuzzer_context,
-                            transformation_sequence_out)
-      .Apply();
-  FuzzerPassSplitBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
-                        transformation_sequence_out)
-      .Apply();
-  FuzzerPassAddDeadBreaks(ir_context.get(), &fact_manager, &fuzzer_context,
-                          transformation_sequence_out)
-      .Apply();
-  FuzzerPassAddDeadContinues(ir_context.get(), &fact_manager, &fuzzer_context,
-                             transformation_sequence_out)
-      .Apply();
-  FuzzerPassObfuscateConstants(ir_context.get(), &fact_manager, &fuzzer_context,
-                               transformation_sequence_out)
-      .Apply();
+  std::vector<std::unique_ptr<FuzzerPass>> passes;
+  while (passes.empty()) {
+    MaybeAddPass<FuzzerPassAddDeadBreaks>(&passes, ir_context.get(),
+                                          &fact_manager, &fuzzer_context,
+                                          transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddDeadContinues>(&passes, ir_context.get(),
+                                             &fact_manager, &fuzzer_context,
+                                             transformation_sequence_out);
+    MaybeAddPass<FuzzerPassApplyIdSynonyms>(&passes, ir_context.get(),
+                                            &fact_manager, &fuzzer_context,
+                                            transformation_sequence_out);
+    MaybeAddPass<FuzzerPassCopyObjects>(&passes, ir_context.get(),
+                                        &fact_manager, &fuzzer_context,
+                                        transformation_sequence_out);
+    MaybeAddPass<FuzzerPassObfuscateConstants>(&passes, ir_context.get(),
+                                               &fact_manager, &fuzzer_context,
+                                               transformation_sequence_out);
+    MaybeAddPass<FuzzerPassPermuteBlocks>(&passes, ir_context.get(),
+                                          &fact_manager, &fuzzer_context,
+                                          transformation_sequence_out);
+    MaybeAddPass<FuzzerPassSplitBlocks>(&passes, ir_context.get(),
+                                        &fact_manager, &fuzzer_context,
+                                        transformation_sequence_out);
+  }
 
-  // Finally, give the blocks in the module a good shake-up.
-  FuzzerPassPermuteBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
-                          transformation_sequence_out)
-      .Apply();
+  bool is_first = true;
+  while (static_cast<uint32_t>(
+             transformation_sequence_out->transformation_size()) <
+             kTransformationLimit &&
+         (is_first ||
+          fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
+    is_first = false;
+    passes[fuzzer_context.RandomIndex(passes)]->Apply();
+  }
 
   // Encode the module as a binary.
   ir_context->module()->ToBinary(binary_out, false);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 8d097b0..69c6e56 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -20,17 +20,16 @@
 namespace fuzz {
 
 namespace {
-// Default probabilities for applying various transformations.
-// All values are percentages.
-// Keep them in alphabetical order.
+// Default <minimum, maximum> pairs of probabilities for applying various
+// transformations. All values are percentages. Keep them in alphabetical order.
 
-const uint32_t kDefaultChanceOfAddingDeadBreak = 20;
-const uint32_t kDefaultChanceOfAddingDeadContinue = 20;
-const uint32_t kDefaultChanceOfCopyingObject = 20;
-const uint32_t kDefaultChanceOfMovingBlockDown = 25;
-const uint32_t kDefaultChanceOfObfuscatingConstant = 20;
-const uint32_t kDefaultChanceOfReplacingIdWithSynonym = 20;
-const uint32_t kDefaultChanceOfSplittingBlock = 20;
+const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadContinue = {5, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
 
 // Default functions for controlling how deep to go during recursive
 // generation/transformation. Keep them in alphabetical order.
@@ -48,16 +47,21 @@
                              uint32_t min_fresh_id)
     : random_generator_(random_generator),
       next_fresh_id_(min_fresh_id),
-      chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak),
-      chance_of_adding_dead_continue_(kDefaultChanceOfAddingDeadContinue),
-      chance_of_copying_object_(kDefaultChanceOfCopyingObject),
-      chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
-      chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant),
-      chance_of_replacing_id_with_synonym_(
-          kDefaultChanceOfReplacingIdWithSynonym),
-      chance_of_splitting_block_(kDefaultChanceOfSplittingBlock),
       go_deeper_in_constant_obfuscation_(
-          kDefaultGoDeeperInConstantObfuscation) {}
+          kDefaultGoDeeperInConstantObfuscation) {
+  chance_of_adding_dead_break_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingDeadBreak);
+  chance_of_adding_dead_continue_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingDeadContinue);
+  chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
+  chance_of_moving_block_down_ =
+      ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
+  chance_of_obfuscating_constant_ =
+      ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
+  chance_of_replacing_id_with_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
+  chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
+}
 
 FuzzerContext::~FuzzerContext() = default;
 
@@ -70,5 +74,12 @@
   return random_generator_->RandomPercentage() < percentage_chance;
 }
 
+uint32_t FuzzerContext::ChooseBetweenMinAndMax(
+    const std::pair<uint32_t, uint32_t>& min_max) {
+  assert(min_max.first <= min_max.second);
+  return min_max.first +
+         random_generator_->RandomUint32(min_max.second - min_max.first + 1);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 90d8756..a1ad66f 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -16,6 +16,7 @@
 #define SOURCE_FUZZ_FUZZER_CONTEXT_H_
 
 #include <functional>
+#include <utility>
 
 #include "source/fuzz/random_generator.h"
 #include "source/opt/function.h"
@@ -45,7 +46,7 @@
   // method, and which must be non-empty.  Typically 'HasSizeMethod' will be an
   // std::vector.
   template <typename HasSizeMethod>
-  uint32_t RandomIndex(HasSizeMethod sequence) {
+  uint32_t RandomIndex(const HasSizeMethod& sequence) {
     assert(sequence.size() > 0);
     return random_generator_->RandomUint32(
         static_cast<uint32_t>(sequence.size()));
@@ -97,6 +98,10 @@
   // or mutating constructs recursively.
   const std::function<bool(uint32_t, RandomGenerator*)>&
       go_deeper_in_constant_obfuscation_;
+
+  // Requires |min_max.first| <= |min_max.second|, and returns a value in the
+  // range [ |min_max.first|, |min_max.second| ]
+  uint32_t ChooseBetweenMinAndMax(const std::pair<uint32_t, uint32_t>& min_max);
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation_move_block_down.cpp b/source/fuzz/transformation_move_block_down.cpp
index ebce185..205818b 100644
--- a/source/fuzz/transformation_move_block_down.cpp
+++ b/source/fuzz/transformation_move_block_down.cpp
@@ -84,13 +84,11 @@
                "To be able to move a block down, it needs to have a "
                "program-order successor.");
         function.MoveBasicBlockToAfter(message_.block_id(), &*block_it);
-        // It is prudent to invalidate analyses after changing block ordering in
-        // case any of them depend on it, but the ones that definitely do not
-        // depend on ordering can be preserved. These include the following,
-        // which can likely be extended.
+        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2889):
+        //  revisit whether it would be OK to avoid invalidating the dominator
+        //  analysis (and perhaps other analyses).
         context->InvalidateAnalysesExceptFor(
-            opt::IRContext::Analysis::kAnalysisDefUse |
-            opt::IRContext::Analysis::kAnalysisDominatorAnalysis);
+            opt::IRContext::Analysis::kAnalysisDefUse);
 
         return;
       }
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index 3c36602..5fb71b0 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -54,7 +54,7 @@
     std::vector<uint32_t> replayer_binary_out;
     protobufs::TransformationSequence replayer_transformation_sequence_out;
 
-    Replayer replayer(env, true);
+    Replayer replayer(env, false);
     replayer.SetMessageConsumer(kSilentConsumer);
     auto replayer_result_status = replayer.Run(
         binary_in, initial_facts, fuzzer_transformation_sequence_out,
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index 933038d..84608f3 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -125,7 +125,7 @@
     const std::vector<uint32_t>& expected_binary_out,
     uint32_t expected_transformations_out_size, uint32_t step_limit) {
   // Run the shrinker.
-  Shrinker shrinker(target_env, step_limit, true);
+  Shrinker shrinker(target_env, step_limit, false);
   shrinker.SetMessageConsumer(kSilentConsumer);
 
   std::vector<uint32_t> binary_out;
diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp
index d33ccba..dc6d4d0 100644
--- a/test/fuzz/transformation_split_block_test.cpp
+++ b/test/fuzz/transformation_split_block_test.cpp
@@ -687,12 +687,12 @@
          %10 = OpVariable %7 Function
                OpStore %8 %9
          %11 = OpLoad %6 %8
-	       OpBranch %20
-	 %20 = OpLabel
-	 %21 = OpPhi %6 %11 %5
-         OpStore %10 %21
-         OpReturn
-         OpFunctionEnd
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %6 %11 %5
+               OpStore %10 %21
+               OpReturn
+               OpFunctionEnd
   )";
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
@@ -732,14 +732,14 @@
          %10 = OpVariable %7 Function
                OpStore %8 %9
          %11 = OpLoad %6 %8
-	       OpBranch %20
-	 %20 = OpLabel
-         OpBranch %100
-  %100 = OpLabel
-	 %21 = OpPhi %6 %11 %20
-         OpStore %10 %21
-         OpReturn
-         OpFunctionEnd
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+         %21 = OpPhi %6 %11 %20
+               OpStore %10 %21
+               OpReturn
+               OpFunctionEnd
   )";
   ASSERT_TRUE(IsEqual(env, after_split, context.get()));
 }