spirv-fuzz: Only recommend passes when a pass had an effect (#3863)

Fixes #3817.
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 7d82884..6fa542c 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -324,8 +324,9 @@
   }
 
   do {
-    if (!ApplyPassAndCheckValidity(repeated_pass_manager->ChoosePass(),
-                                   tools)) {
+    if (!ApplyPassAndCheckValidity(
+            repeated_pass_manager->ChoosePass(transformation_sequence_out_),
+            tools)) {
       return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule,
               std::vector<uint32_t>(), protobufs::TransformationSequence()};
     }
diff --git a/source/fuzz/pass_management/repeated_pass_manager.h b/source/fuzz/pass_management/repeated_pass_manager.h
index 29b5fcc..1c23179 100644
--- a/source/fuzz/pass_management/repeated_pass_manager.h
+++ b/source/fuzz/pass_management/repeated_pass_manager.h
@@ -18,6 +18,7 @@
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass.h"
 #include "source/fuzz/pass_management/repeated_pass_instances.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -33,8 +34,11 @@
 
   virtual ~RepeatedPassManager();
 
-  // Returns the fuzzer pass instance that should be run next.
-  virtual FuzzerPass* ChoosePass() = 0;
+  // Returns the fuzzer pass instance that should be run next.  The
+  // transformations that have been applied so far are provided via
+  // |applied_transformations| and can be used to influence the decision.
+  virtual FuzzerPass* ChoosePass(
+      const protobufs::TransformationSequence& applied_transformations) = 0;
 
  protected:
   FuzzerContext* GetFuzzerContext() { return fuzzer_context_; }
diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp
index e8bd545..e91ba35 100644
--- a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp
+++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp
@@ -21,7 +21,9 @@
     RepeatedPassManagerLoopedWithRecommendations(
         FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances,
         RepeatedPassRecommender* pass_recommender)
-    : RepeatedPassManager(fuzzer_context, pass_instances), next_pass_index_(0) {
+    : RepeatedPassManager(fuzzer_context, pass_instances),
+      num_transformations_applied_before_last_pass_choice_(0),
+      next_pass_index_(0) {
   auto& passes = GetPassInstances()->GetPasses();
   do {
     FuzzerPass* current_pass =
@@ -29,6 +31,8 @@
     pass_loop_.push_back(current_pass);
     for (auto future_pass :
          pass_recommender->GetFuturePassRecommendations(*current_pass)) {
+      recommended_pass_indices_.insert(
+          static_cast<uint32_t>(pass_loop_.size()));
       pass_loop_.push_back(future_pass);
     }
   } while (fuzzer_context->ChoosePercentage(
@@ -38,7 +42,24 @@
 RepeatedPassManagerLoopedWithRecommendations::
     ~RepeatedPassManagerLoopedWithRecommendations() = default;
 
-FuzzerPass* RepeatedPassManagerLoopedWithRecommendations::ChoosePass() {
+FuzzerPass* RepeatedPassManagerLoopedWithRecommendations::ChoosePass(
+    const protobufs::TransformationSequence& applied_transformations) {
+  assert((next_pass_index_ > 0 ||
+          recommended_pass_indices_.count(next_pass_index_) == 0) &&
+         "The first pass in the loop should not be a recommendation.");
+  assert(static_cast<uint32_t>(applied_transformations.transformation_size()) >=
+             num_transformations_applied_before_last_pass_choice_ &&
+         "The number of applied transformations should not decrease.");
+  if (num_transformations_applied_before_last_pass_choice_ ==
+      static_cast<uint32_t>(applied_transformations.transformation_size())) {
+    // The last pass that was applied did not lead to any new transformations.
+    // We thus do not want to apply recommendations based on it, so we skip on
+    // to the next non-recommended pass.
+    while (recommended_pass_indices_.count(next_pass_index_)) {
+      next_pass_index_ =
+          (next_pass_index_ + 1) % static_cast<uint32_t>(pass_loop_.size());
+    }
+  }
   auto result = pass_loop_[next_pass_index_];
   next_pass_index_ =
       (next_pass_index_ + 1) % static_cast<uint32_t>(pass_loop_.size());
diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h
index 25cef06..42ce38e 100644
--- a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h
+++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h
@@ -41,12 +41,23 @@
 
   ~RepeatedPassManagerLoopedWithRecommendations() override;
 
-  FuzzerPass* ChoosePass() override;
+  FuzzerPass* ChoosePass(const protobufs::TransformationSequence&
+                             applied_transformations) override;
 
  private:
   // The loop of fuzzer passes to be applied, populated on construction.
   std::vector<FuzzerPass*> pass_loop_;
 
+  // A set of indices into |pass_loop_| recording which passes are in the loop
+  // because they are recommended based on previous passes in the loop.  This
+  // allows these recommended passes to be skipped if the passes they are
+  // meant to amplify had no effect.
+  std::unordered_set<uint32_t> recommended_pass_indices_;
+
+  // Used to detect when chosen passes have had no effect, so that their
+  // associated recommendations are skipped.
+  uint32_t num_transformations_applied_before_last_pass_choice_;
+
   // An index into |pass_loop_| specifying which pass should be served up next
   // time ChoosePass is invoked.
   uint32_t next_pass_index_;
diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp
index 48b1e6a..920d13f 100644
--- a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp
+++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp
@@ -22,12 +22,29 @@
         FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances,
         RepeatedPassRecommender* pass_recommender)
     : RepeatedPassManager(fuzzer_context, pass_instances),
-      pass_recommender_(pass_recommender) {}
+      pass_recommender_(pass_recommender),
+      num_transformations_applied_before_last_pass_choice_(0),
+      last_pass_choice_(nullptr) {}
 
 RepeatedPassManagerRandomWithRecommendations::
     ~RepeatedPassManagerRandomWithRecommendations() = default;
 
-FuzzerPass* RepeatedPassManagerRandomWithRecommendations::ChoosePass() {
+FuzzerPass* RepeatedPassManagerRandomWithRecommendations::ChoosePass(
+    const protobufs::TransformationSequence& applied_transformations) {
+  assert(static_cast<uint32_t>(applied_transformations.transformation_size()) >=
+             num_transformations_applied_before_last_pass_choice_ &&
+         "The number of applied transformations should not decrease.");
+  if (last_pass_choice_ != nullptr &&
+      static_cast<uint32_t>(applied_transformations.transformation_size()) >
+          num_transformations_applied_before_last_pass_choice_) {
+    // The last pass had some effect, so we make future recommendations based on
+    // it.
+    for (auto future_pass :
+         pass_recommender_->GetFuturePassRecommendations(*last_pass_choice_)) {
+      recommended_passes_.push_back(future_pass);
+    }
+  }
+
   FuzzerPass* result;
   if (recommended_passes_.empty() || GetFuzzerContext()->ChooseEven()) {
     auto& passes = GetPassInstances()->GetPasses();
@@ -36,10 +53,6 @@
     result = recommended_passes_.front();
     recommended_passes_.pop_front();
   }
-  for (auto future_pass :
-       pass_recommender_->GetFuturePassRecommendations(*result)) {
-    recommended_passes_.push_back(future_pass);
-  }
   return result;
 }
 
diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h
index acd207b..5dbd455 100644
--- a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h
+++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h
@@ -42,7 +42,8 @@
 
   ~RepeatedPassManagerRandomWithRecommendations() override;
 
-  FuzzerPass* ChoosePass() override;
+  FuzzerPass* ChoosePass(const protobufs::TransformationSequence&
+                             applied_transformations) override;
 
  private:
   // The queue of passes that have been recommended based on previously-chosen
@@ -51,6 +52,14 @@
 
   // Used to recommend future passes.
   RepeatedPassRecommender* pass_recommender_;
+
+  // Used to detect when chosen passes have had no effect, so that their
+  // associated recommendations are skipped.
+  uint32_t num_transformations_applied_before_last_pass_choice_;
+
+  // The fuzzer pass returned last time ChoosePass() was called; nullptr if
+  // ChoosePass() has not yet been called.
+  FuzzerPass* last_pass_choice_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.cpp b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp
index a85a732..c593ffe 100644
--- a/source/fuzz/pass_management/repeated_pass_manager_simple.cpp
+++ b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp
@@ -23,7 +23,8 @@
 
 RepeatedPassManagerSimple::~RepeatedPassManagerSimple() = default;
 
-FuzzerPass* RepeatedPassManagerSimple::ChoosePass() {
+FuzzerPass* RepeatedPassManagerSimple::ChoosePass(
+    const protobufs::TransformationSequence& /*unused*/) {
   auto& passes = GetPassInstances()->GetPasses();
   return passes[GetFuzzerContext()->RandomIndex(passes)].get();
 }
diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.h b/source/fuzz/pass_management/repeated_pass_manager_simple.h
index 548f77b..a4cc652 100644
--- a/source/fuzz/pass_management/repeated_pass_manager_simple.h
+++ b/source/fuzz/pass_management/repeated_pass_manager_simple.h
@@ -29,7 +29,8 @@
 
   ~RepeatedPassManagerSimple() override;
 
-  FuzzerPass* ChoosePass() override;
+  FuzzerPass* ChoosePass(const protobufs::TransformationSequence&
+                             applied_transformations) override;
 };
 
 }  // namespace fuzz