spirv-fuzz: Do not add too many dead blocks (#4217)

Profiling has shown that adding large numbers of dead block
transformations can be expensive because each on requires dominator
analysis information, and each one invalidates this information. There
is currently no obvious mechanism for incrementally updating the
dominator analysis. This change restricts the number of these
transformations that a single fuzzer pass will apply, to restrict this
performance bottleneck.
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
index 20d4418..8c166a2 100644
--- a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
@@ -14,12 +14,20 @@
 
 #include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
 
+#include <algorithm>
+
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_add_dead_block.h"
 
 namespace spvtools {
 namespace fuzz {
 
+namespace {
+
+const size_t kMaxTransformationsInOnePass = 100U;
+
+}  // namespace
+
 FuzzerPassAddDeadBlocks::FuzzerPassAddDeadBlocks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
@@ -55,9 +63,18 @@
           GetFuzzerContext()->GetFreshId(), block.id(), condition_value));
     }
   }
-  // Apply all those transformations that are in fact applicable.
-  for (auto& transformation : candidate_transformations) {
-    MaybeApplyTransformation(transformation);
+  // Applying transformations can be expensive as each transformation requires
+  // dominator information and also invalidates dominator information. We thus
+  // limit the number of transformations that one application of this fuzzer
+  // pass can apply. We choose to do this after identifying all the
+  // transformations that we *might* want to apply, rather than breaking the
+  // above loops once the limit is reached, to avoid biasing towards
+  // transformations that target early parts of the module.
+  GetFuzzerContext()->Shuffle(&candidate_transformations);
+  for (size_t i = 0; i < std::min(kMaxTransformationsInOnePass,
+                                  candidate_transformations.size());
+       i++) {
+    MaybeApplyTransformation(candidate_transformations[i]);
   }
 }