spirv-fuzz: Integrate spirv-reduce with shrinker (#3849)

This extends shrinking so that spirv-reduce is employed to simplify
the functions that are added by TransformationAddFunction.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index ef699a7..050bd96 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -36,6 +36,7 @@
   )
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
+        added_function_reducer.h
         call_graph.h
         comparator_deep_blocks_first.h
         counter_overflow_id_source.h
@@ -218,6 +219,7 @@
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
+        added_function_reducer.cpp
         call_graph.cpp
         counter_overflow_id_source.cpp
         data_descriptor.cpp
@@ -429,6 +431,7 @@
   target_link_libraries(SPIRV-Tools-fuzz
         PUBLIC ${SPIRV_TOOLS}-static
         PUBLIC SPIRV-Tools-opt
+        PUBLIC SPIRV-Tools-reduce
         PUBLIC protobuf::libprotobuf)
 
   set_property(TARGET SPIRV-Tools-fuzz PROPERTY FOLDER "SPIRV-Tools libraries")
diff --git a/source/fuzz/added_function_reducer.cpp b/source/fuzz/added_function_reducer.cpp
new file mode 100644
index 0000000..0ea88c8
--- /dev/null
+++ b/source/fuzz/added_function_reducer.cpp
@@ -0,0 +1,293 @@
+// 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/added_function_reducer.h"
+
+#include "source/fuzz/instruction_message.h"
+#include "source/fuzz/replayer.h"
+#include "source/fuzz/transformation_add_function.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "source/reduce/reducer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+AddedFunctionReducer::AddedFunctionReducer(
+    spv_target_env target_env, MessageConsumer consumer,
+    const std::vector<uint32_t>& binary_in,
+    const protobufs::FactSequence& initial_facts,
+    const protobufs::TransformationSequence& transformation_sequence_in,
+    uint32_t index_of_add_function_transformation,
+    const Shrinker::InterestingnessFunction& shrinker_interestingness_function,
+    bool validate_during_replay, spv_validator_options validator_options,
+    uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts)
+    : target_env_(target_env),
+      consumer_(std::move(consumer)),
+      binary_in_(binary_in),
+      initial_facts_(initial_facts),
+      transformation_sequence_in_(transformation_sequence_in),
+      index_of_add_function_transformation_(
+          index_of_add_function_transformation),
+      shrinker_interestingness_function_(shrinker_interestingness_function),
+      validate_during_replay_(validate_during_replay),
+      validator_options_(validator_options),
+      shrinker_step_limit_(shrinker_step_limit),
+      num_existing_shrink_attempts_(num_existing_shrink_attempts),
+      num_reduction_attempts_(0) {}
+
+AddedFunctionReducer::~AddedFunctionReducer() = default;
+
+AddedFunctionReducer::AddedFunctionReducerResult AddedFunctionReducer::Run() {
+  // Replay all transformations before the AddFunction transformation, then
+  // add the raw function associated with the AddFunction transformation.
+  std::vector<uint32_t> binary_to_reduce;
+  std::unordered_set<uint32_t> irrelevant_pointee_global_variables;
+  ReplayPrefixAndAddFunction(&binary_to_reduce,
+                             &irrelevant_pointee_global_variables);
+
+  // Set up spirv-reduce to use our very specific interestingness function.
+  reduce::Reducer reducer(target_env_);
+  reducer.SetMessageConsumer(consumer_);
+  reducer.AddDefaultReductionPasses();
+  reducer.SetInterestingnessFunction(
+      [this, &irrelevant_pointee_global_variables](
+          const std::vector<uint32_t>& binary_under_reduction,
+          uint32_t /*unused*/) {
+        return InterestingnessFunctionForReducingAddedFunction(
+            binary_under_reduction, irrelevant_pointee_global_variables);
+      });
+
+  // Instruct spirv-reduce to only target the function with the id associated
+  // with the AddFunction transformation that we care about.
+  spvtools::ReducerOptions reducer_options;
+  reducer_options.set_target_function(GetAddedFunctionId());
+  // Bound the number of reduction steps that spirv-reduce can make according
+  // to the overall shrinker step limit and the number of shrink attempts that
+  // have already been tried.
+  assert(shrinker_step_limit_ > num_existing_shrink_attempts_ &&
+         "The added function reducer should not have been invoked.");
+  reducer_options.set_step_limit(shrinker_step_limit_ -
+                                 num_existing_shrink_attempts_);
+
+  // Run spirv-reduce.
+  std::vector<uint32_t> reduced_binary;
+  auto reducer_result =
+      reducer.Run(std::move(binary_to_reduce), &reduced_binary, reducer_options,
+                  validator_options_);
+  if (reducer_result != reduce::Reducer::kComplete &&
+      reducer_result != reduce::Reducer::kReachedStepLimit) {
+    return {AddedFunctionReducerResultStatus::kReductionFailed,
+            std::vector<uint32_t>(), protobufs::TransformationSequence(), 0};
+  }
+
+  // Provide the outer shrinker with an adapted sequence of transformations in
+  // which the AddFunction transformation of interest has been simplified to use
+  // the version of the added function that appears in |reduced_binary|.
+  std::vector<uint32_t> binary_out;
+  protobufs::TransformationSequence transformation_sequence_out;
+  ReplayAdaptedTransformations(reduced_binary, &binary_out,
+                               &transformation_sequence_out);
+  return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
+          std::move(transformation_sequence_out), num_reduction_attempts_};
+}
+
+bool AddedFunctionReducer::InterestingnessFunctionForReducingAddedFunction(
+    const std::vector<uint32_t>& binary_under_reduction,
+    const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables) {
+  uint32_t counter_for_shrinker_interestingness_function =
+      num_existing_shrink_attempts_ + num_reduction_attempts_;
+  num_reduction_attempts_++;
+
+  // The reduced version of the added function must be limited to accessing
+  // global variables appearing in |irrelevant_pointee_global_variables|.  This
+  // is to guard against the possibility of spirv-reduce changing a reference
+  // to an irrelevant global to a reference to a regular global variable, which
+  // could cause the added function to change the semantics of the original
+  // module.
+  auto ir_context =
+      BuildModule(target_env_, consumer_, binary_under_reduction.data(),
+                  binary_under_reduction.size());
+  assert(ir_context != nullptr && "The binary should be parsable.");
+  for (auto& type_or_value : ir_context->module()->types_values()) {
+    if (type_or_value.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (irrelevant_pointee_global_variables.count(type_or_value.result_id())) {
+      continue;
+    }
+    if (!ir_context->get_def_use_mgr()->WhileEachUse(
+            &type_or_value,
+            [this, &ir_context](opt::Instruction* user,
+                                uint32_t /*unused*/) -> bool {
+              auto block = ir_context->get_instr_block(user);
+              if (block != nullptr &&
+                  block->GetParent()->result_id() == GetAddedFunctionId()) {
+                return false;
+              }
+              return true;
+            })) {
+      return false;
+    }
+  }
+
+  // For the binary to be deemed interesting, it must be possible to
+  // successfully apply all the transformations, with the transformation at
+  // index |index_of_add_function_transformation_| simplified to use the version
+  // of the added function from |binary_under_reduction|.
+  //
+  // This might not be the case: spirv-reduce might have removed a chunk of the
+  // added function on which future transformations depend.
+  //
+  // This is an optimization: the assumption is that having already shrunk the
+  // transformation sequence down to minimal form, all transformations have a
+  // role to play, and it's almost certainly a waste of time to invoke the
+  // shrinker's interestingness function if we have eliminated transformations
+  // that the shrinker previously tried to -- but could not -- eliminate.
+  std::vector<uint32_t> binary_out;
+  protobufs::TransformationSequence modified_transformations;
+  ReplayAdaptedTransformations(binary_under_reduction, &binary_out,
+                               &modified_transformations);
+  if (transformation_sequence_in_.transformation_size() !=
+      modified_transformations.transformation_size()) {
+    return false;
+  }
+
+  // The resulting binary must be deemed interesting according to the shrinker's
+  // interestingness function.
+  return shrinker_interestingness_function_(
+      binary_out, counter_for_shrinker_interestingness_function);
+}
+
+void AddedFunctionReducer::ReplayPrefixAndAddFunction(
+    std::vector<uint32_t>* binary_out,
+    std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const {
+  assert(transformation_sequence_in_
+             .transformation(index_of_add_function_transformation_)
+             .has_add_function() &&
+         "A TransformationAddFunction is required at the given index.");
+
+  auto replay_result = Replayer(target_env_, consumer_, binary_in_,
+                                initial_facts_, transformation_sequence_in_,
+                                index_of_add_function_transformation_,
+                                validate_during_replay_, validator_options_)
+                           .Run();
+  assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
+         "Replay should succeed");
+  assert(static_cast<uint32_t>(
+             replay_result.applied_transformations.transformation_size()) ==
+             index_of_add_function_transformation_ &&
+         "All requested transformations should have applied.");
+
+  auto* ir_context = replay_result.transformed_module.get();
+
+  for (auto& type_or_value : ir_context->module()->types_values()) {
+    if (type_or_value.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (replay_result.transformation_context->GetFactManager()
+            ->PointeeValueIsIrrelevant(type_or_value.result_id())) {
+      irrelevant_pointee_global_variables->insert(type_or_value.result_id());
+    }
+  }
+
+  // Add the function associated with the transformation at
+  // |index_of_add_function_transformation| to the module.  By construction this
+  // should succeed.
+  const protobufs::TransformationAddFunction&
+      transformation_add_function_message =
+          transformation_sequence_in_
+              .transformation(index_of_add_function_transformation_)
+              .add_function();
+  bool success = TransformationAddFunction(transformation_add_function_message)
+                     .TryToAddFunction(ir_context);
+  (void)success;  // Keep release mode compilers happy.
+  assert(success && "Addition of the function should have succeeded.");
+
+  // Get the binary representation of the module with this function added.
+  ir_context->module()->ToBinary(binary_out, false);
+}
+
+void AddedFunctionReducer::ReplayAdaptedTransformations(
+    const std::vector<uint32_t>& binary_under_reduction,
+    std::vector<uint32_t>* binary_out,
+    protobufs::TransformationSequence* transformation_sequence_out) const {
+  assert(index_of_add_function_transformation_ <
+             static_cast<uint32_t>(
+                 transformation_sequence_in_.transformation_size()) &&
+         "The relevant add function transformation must be present.");
+  std::unique_ptr<opt::IRContext> ir_context_under_reduction =
+      BuildModule(target_env_, consumer_, binary_under_reduction.data(),
+                  binary_under_reduction.size());
+  assert(ir_context_under_reduction && "Error building module.");
+
+  protobufs::TransformationSequence modified_transformations;
+  for (uint32_t i = 0;
+       i <
+       static_cast<uint32_t>(transformation_sequence_in_.transformation_size());
+       i++) {
+    if (i == index_of_add_function_transformation_) {
+      protobufs::TransformationAddFunction modified_add_function =
+          transformation_sequence_in_
+              .transformation(index_of_add_function_transformation_)
+              .add_function();
+      assert(GetAddedFunctionId() ==
+                 modified_add_function.instruction(0).result_id() &&
+             "Unexpected result id for added function.");
+      modified_add_function.clear_instruction();
+      for (auto& function : *ir_context_under_reduction->module()) {
+        if (function.result_id() != GetAddedFunctionId()) {
+          continue;
+        }
+        function.ForEachInst(
+            [&modified_add_function](const opt::Instruction* instruction) {
+              *modified_add_function.add_instruction() =
+                  MakeInstructionMessage(instruction);
+            });
+      }
+      assert(modified_add_function.instruction_size() > 0 &&
+             "Some instructions for the added function should remain.");
+      *modified_transformations.add_transformation()->mutable_add_function() =
+          modified_add_function;
+    } else {
+      *modified_transformations.add_transformation() =
+          transformation_sequence_in_.transformation(i);
+    }
+  }
+  assert(
+      transformation_sequence_in_.transformation_size() ==
+          modified_transformations.transformation_size() &&
+      "The original and modified transformations should have the same size.");
+  auto replay_result = Replayer(target_env_, consumer_, binary_in_,
+                                initial_facts_, modified_transformations,
+                                modified_transformations.transformation_size(),
+                                validate_during_replay_, validator_options_)
+                           .Run();
+  assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
+         "Replay should succeed.");
+  replay_result.transformed_module->module()->ToBinary(binary_out, false);
+  *transformation_sequence_out =
+      std::move(replay_result.applied_transformations);
+}
+
+uint32_t AddedFunctionReducer::GetAddedFunctionId() const {
+  return transformation_sequence_in_
+      .transformation(index_of_add_function_transformation_)
+      .add_function()
+      .instruction(0)
+      .result_id();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/added_function_reducer.h b/source/fuzz/added_function_reducer.h
new file mode 100644
index 0000000..9dcc632
--- /dev/null
+++ b/source/fuzz/added_function_reducer.h
@@ -0,0 +1,192 @@
+// 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_ADDED_FUNCTION_REDUCER_H_
+#define SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
+
+#include <unordered_set>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/shrinker.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace fuzz {
+
+// An auxiliary class used by Shrinker, this class takes care of using
+// spirv-reduce to reduce the body of a function encoded in an AddFunction
+// transformation, in case a smaller, simpler function can be added instead.
+class AddedFunctionReducer {
+ public:
+  // Possible statuses that can result from running the shrinker.
+  enum class AddedFunctionReducerResultStatus {
+    kComplete,
+    kReductionFailed,
+  };
+
+  struct AddedFunctionReducerResult {
+    AddedFunctionReducerResultStatus status;
+    std::vector<uint32_t> transformed_binary;
+    protobufs::TransformationSequence applied_transformations;
+    uint32_t num_reduction_attempts;
+  };
+
+  AddedFunctionReducer(
+      spv_target_env target_env, MessageConsumer consumer,
+      const std::vector<uint32_t>& binary_in,
+      const protobufs::FactSequence& initial_facts,
+      const protobufs::TransformationSequence& transformation_sequence_in,
+      uint32_t index_of_add_function_transformation,
+      const Shrinker::InterestingnessFunction&
+          shrinker_interestingness_function,
+      bool validate_during_replay, spv_validator_options validator_options,
+      uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts);
+
+  // Disables copy/move constructor/assignment operations.
+  AddedFunctionReducer(const AddedFunctionReducer&) = delete;
+  AddedFunctionReducer(AddedFunctionReducer&&) = delete;
+  AddedFunctionReducer& operator=(const AddedFunctionReducer&) = delete;
+  AddedFunctionReducer& operator=(AddedFunctionReducer&&) = delete;
+
+  ~AddedFunctionReducer();
+
+  // Invokes spirv-reduce on the function in the AddFunction transformation
+  // identified by |index_of_add_function_transformation|.  Returns a sequence
+  // of transformations identical to |transformation_sequence_in|, except that
+  // the AddFunction transformation at |index_of_add_function_transformation|
+  // might have been simplified.  The binary associated with applying the
+  // resulting sequence of transformations to |binary_in| is also returned, as
+  // well as the number of reduction steps that spirv-reduce made.
+  //
+  // On failure, an empty transformation sequence and binary are returned,
+  // with a placeholder value of 0 for the number of reduction attempts.
+  AddedFunctionReducerResult Run();
+
+ private:
+  // Yields, via |binary_out|, the binary obtained by applying transformations
+  // [0, |index_of_added_function_| - 1] from |transformations_in_| to
+  // |binary_in_|, and then adding the raw function encoded in
+  // |transformations_in_[index_of_added_function_]| (without adapting that
+  // function to make it livesafe).  This function has |added_function_id_| as
+  // its result id.
+  //
+  // The ids associated with all global variables in |binary_out| that had the
+  // "irrelevant pointee value" fact are also returned via
+  // |irrelevant_pointee_global_variables|.
+  //
+  // The point of this function is that spirv-reduce can subsequently be applied
+  // to function |added_function_id_| in |binary_out|.  By construction,
+  // |added_function_id_| should originally manipulate globals for which
+  // "irrelevant pointee value" facts hold.  The set
+  // |irrelevant_pointee_global_variables| can be used to force spirv-reduce
+  // to preserve this, to avoid the reduced function ending up manipulating
+  // other global variables of the SPIR-V module, potentially changing their
+  // value and thus changing the semantics of the module.
+  void ReplayPrefixAndAddFunction(
+      std::vector<uint32_t>* binary_out,
+      std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const;
+
+  // This is the interestingness function that will be used by spirv-reduce
+  // when shrinking the added function.
+  //
+  // For |binary_under_reduction| to be deemed interesting, the following
+  // conditions must hold:
+  // - The function with id |added_function_id_| in |binary_under_reduction|
+  //   must only reference global variables in
+  //   |irrelevant_pointee_global_variables|.  This avoids the reduced function
+  //   changing the semantics of the original SPIR-V module.
+  // - It must be possible to successfully replay the transformations in
+  //   |transformation_sequence_in_|, adapted so that the function added by the
+  //   transformation at |index_of_add_function_transformation_| is replaced by
+  //   the function with id |added_function_id_| in |binary_under_reduction|,
+  //   to |binary_in| (starting with initial facts |initial_facts_|).
+  // - All the transformations in this sequence must be successfully applied
+  //   during replay.
+  // - The resulting binary must be interesting according to
+  //   |shrinker_interestingness_function_|.
+  bool InterestingnessFunctionForReducingAddedFunction(
+      const std::vector<uint32_t>& binary_under_reduction,
+      const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables);
+
+  // Starting with |binary_in_| and |initial_facts_|, the transformations in
+  // |transformation_sequence_in_| are replayed.  However, the transformation
+  // at index |index_of_add_function_transformation_| of
+  // |transformation_sequence_in_| -- which is guaranteed to be an AddFunction
+  // transformation -- is adapted so that the function to be added is replaced
+  // with the function in |binary_under_reduction| with id |added_function_id_|.
+  //
+  // The binary resulting from this replay is returned via |binary_out|, and the
+  // adapted transformation sequence via |transformation_sequence_out|.
+  void ReplayAdaptedTransformations(
+      const std::vector<uint32_t>& binary_under_reduction,
+      std::vector<uint32_t>* binary_out,
+      protobufs::TransformationSequence* transformation_sequence_out) const;
+
+  // Returns the id of the function to be added by the AddFunction
+  // transformation at
+  // |transformation_sequence_in_[index_of_add_function_transformation_]|.
+  uint32_t GetAddedFunctionId() const;
+
+  // Target environment.
+  const spv_target_env target_env_;
+
+  // Message consumer.
+  MessageConsumer consumer_;
+
+  // The initial binary to which transformations are applied -- i.e., the
+  // binary to which spirv-fuzz originally applied transformations.
+  const std::vector<uint32_t>& binary_in_;
+
+  // Initial facts about |binary_in_|.
+  const protobufs::FactSequence& initial_facts_;
+
+  // A set of transformations that can be successfully applied to |binary_in_|.
+  const protobufs::TransformationSequence& transformation_sequence_in_;
+
+  // An index into |transformation_sequence_in_| referring to an AddFunction
+  // transformation.  This is the transformation to be simplified using
+  // spirv-reduce.
+  const uint32_t index_of_add_function_transformation_;
+
+  // The interestingness function that has been provided to guide the
+  // overall shrinking process.  The AddFunction transformation being simplified
+  // by this class should still -- when applied in conjunction with the other
+  // transformations in |transformation_sequence_in_| -- lead to a binary that
+  // is deemed interesting by this function.
+  const Shrinker::InterestingnessFunction& shrinker_interestingness_function_;
+
+  // 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_;
+
+  // The step limit associated with the overall shrinking process.
+  const uint32_t shrinker_step_limit_;
+
+  // The number of shrink attempts that had been applied prior to invoking this
+  // AddedFunctionReducer instance.
+  const uint32_t num_existing_shrink_attempts_;
+
+  // Tracks the number of attempts that spirv-reduce has made in reducing the
+  // added function.
+  uint32_t num_reduction_attempts_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
diff --git a/source/fuzz/instruction_message.cpp b/source/fuzz/instruction_message.cpp
index 44777ae..9503932 100644
--- a/source/fuzz/instruction_message.cpp
+++ b/source/fuzz/instruction_message.cpp
@@ -36,6 +36,18 @@
   return result;
 }
 
+protobufs::Instruction MakeInstructionMessage(
+    const opt::Instruction* instruction) {
+  opt::Instruction::OperandList input_operands;
+  for (uint32_t input_operand_index = 0;
+       input_operand_index < instruction->NumInOperands();
+       input_operand_index++) {
+    input_operands.push_back(instruction->GetInOperand(input_operand_index));
+  }
+  return MakeInstructionMessage(instruction->opcode(), instruction->type_id(),
+                                instruction->result_id(), input_operands);
+}
+
 std::unique_ptr<opt::Instruction> InstructionFromMessage(
     opt::IRContext* ir_context,
     const protobufs::Instruction& instruction_message) {
diff --git a/source/fuzz/instruction_message.h b/source/fuzz/instruction_message.h
index c010c2f..fcbb4c7 100644
--- a/source/fuzz/instruction_message.h
+++ b/source/fuzz/instruction_message.h
@@ -29,6 +29,10 @@
     SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
     const opt::Instruction::OperandList& input_operands);
 
+// Creates an Instruction protobuf message from a parsed instruction.
+protobufs::Instruction MakeInstructionMessage(
+    const opt::Instruction* instruction);
+
 // Creates and returns an opt::Instruction from protobuf message
 // |instruction_message|, relative to |ir_context|.  In the process, the module
 // id bound associated with |ir_context| is updated to be at least as large as
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index 1b65e40..c1e2514 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -16,6 +16,7 @@
 
 #include <sstream>
 
+#include "source/fuzz/added_function_reducer.h"
 #include "source/fuzz/pseudo_random_generator.h"
 #include "source/fuzz/replayer.h"
 #include "source/opt/build_module.h"
@@ -238,6 +239,51 @@
     }
   }
 
+  // We now use spirv-reduce to minimise the functions associated with any
+  // AddFunction transformations that remain.
+  //
+  // Consider every remaining transformation.
+  for (uint32_t transformation_index = 0;
+       attempt < step_limit_ &&
+       transformation_index <
+           static_cast<uint32_t>(
+               current_best_transformations.transformation_size());
+       transformation_index++) {
+    // Skip all transformations apart from TransformationAddFunction.
+    if (!current_best_transformations.transformation(transformation_index)
+             .has_add_function()) {
+      continue;
+    }
+    // Invoke spirv-reduce on the function encoded in this AddFunction
+    // transformation.  The details of this are rather involved, and so are
+    // encapsulated in a separate class.
+    auto added_function_reducer_result =
+        AddedFunctionReducer(target_env_, consumer_, binary_in_, initial_facts_,
+                             current_best_transformations, transformation_index,
+                             interestingness_function_, validate_during_replay_,
+                             validator_options_, step_limit_, attempt)
+            .Run();
+    // Reducing the added function should succeed.  If it doesn't, we report
+    // a shrinking error.
+    if (added_function_reducer_result.status !=
+        AddedFunctionReducer::AddedFunctionReducerResultStatus::kComplete) {
+      return {ShrinkerResultStatus::kAddedFunctionReductionFailed,
+              std::vector<uint32_t>(), protobufs::TransformationSequence()};
+    }
+    assert(current_best_transformations.transformation_size() ==
+               added_function_reducer_result.applied_transformations
+                   .transformation_size() &&
+           "The number of transformations should not have changed.");
+    current_best_binary =
+        std::move(added_function_reducer_result.transformed_binary);
+    current_best_transformations =
+        std::move(added_function_reducer_result.applied_transformations);
+    // The added function reducer reports how many reduction attempts
+    // spirv-reduce took when reducing the function.  We regard each of these
+    // as a shrinker attempt.
+    attempt += added_function_reducer_result.num_reduction_attempts;
+  }
+
   // Indicate whether shrinking completed or was truncated due to reaching the
   // step limit.
   //
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
index 982a843..b9015d6 100644
--- a/source/fuzz/shrinker.h
+++ b/source/fuzz/shrinker.h
@@ -37,6 +37,7 @@
     kInitialBinaryNotInteresting,
     kReplayFailed,
     kStepLimitReached,
+    kAddedFunctionReductionFailed,
   };
 
   struct ShrinkerResult {
@@ -107,7 +108,7 @@
   // The series of transformations to be shrunk.
   const protobufs::TransformationSequence& transformation_sequence_in_;
 
-  // Function that decides whether a given binary is interesting.
+  // Function that decides whether a given module is interesting.
   const InterestingnessFunction& interestingness_function_;
 
   // Step limit to decide when to terminate shrinking early.
diff --git a/source/fuzz/transformation_add_function.h b/source/fuzz/transformation_add_function.h
index 3a4bd18..e5381d1 100644
--- a/source/fuzz/transformation_add_function.h
+++ b/source/fuzz/transformation_add_function.h
@@ -73,7 +73,6 @@
   static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context,
                                      uint32_t loop_header_block_id);
 
- private:
   // Attempts to create a function from the series of instructions in
   // |message_.instruction| and add it to |ir_context|.
   //
@@ -94,6 +93,7 @@
   //   to add the function.
   bool TryToAddFunction(opt::IRContext* ir_context) const;
 
+ private:
   // Should only be called if |message_.is_livesafe| holds.  Attempts to make
   // the function livesafe (see FactFunctionIsLivesafe for a definition).
   // Returns false if this is not possible, due to |message_| or |ir_context|
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index e287d8d..89ffa59 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -30,6 +30,7 @@
           instruction_descriptor_test.cpp
           fuzzer_pass_test.cpp
           replayer_test.cpp
+          shrinker_test.cpp
           transformation_access_chain_test.cpp
           transformation_add_bit_instruction_synonym_test.cpp
           transformation_add_constant_boolean_test.cpp
diff --git a/test/fuzz/shrinker_test.cpp b/test/fuzz/shrinker_test.cpp
new file mode 100644
index 0000000..e5e3776
--- /dev/null
+++ b/test/fuzz/shrinker_test.cpp
@@ -0,0 +1,266 @@
+// 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/shrinker.h"
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+#include "source/util/make_unique.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(ShrinkerTest, ReduceAddedFunctions) {
+  const std::string kReferenceModule = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Private %6
+          %8 = OpVariable %7 Private
+          %9 = OpConstant %6 2
+         %10 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+               OpStore %8 %9
+         %12 = OpLoad %6 %8
+               OpStore %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string kDonorModule = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %12 = OpTypeFunction %2 %7
+         %17 = OpConstant %6 0
+         %26 = OpTypeBool
+         %32 = OpConstant %6 1
+         %46 = OpTypePointer Private %6
+         %47 = OpVariable %46 Private
+         %48 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %49 = OpVariable %7 Function
+         %50 = OpVariable %7 Function
+         %51 = OpLoad %6 %49
+               OpStore %50 %51
+         %52 = OpFunctionCall %2 %14 %50
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %16 = OpVariable %7 Function
+         %18 = OpVariable %7 Function
+               OpStore %16 %17
+               OpStore %18 %17
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+         %24 = OpLoad %6 %18
+         %25 = OpLoad %6 %9
+         %27 = OpSLessThan %26 %24 %25
+               OpBranchConditional %27 %20 %21
+         %20 = OpLabel
+         %28 = OpLoad %6 %9
+         %29 = OpLoad %6 %16
+         %30 = OpIAdd %6 %29 %28
+               OpStore %16 %30
+               OpBranch %22
+         %22 = OpLabel
+         %31 = OpLoad %6 %18
+         %33 = OpIAdd %6 %31 %32
+               OpStore %18 %33
+               OpBranch %19
+         %21 = OpLabel
+         %34 = OpLoad %6 %16
+         %35 = OpNot %6 %34
+               OpReturnValue %35
+               OpFunctionEnd
+         %14 = OpFunction %2 None %12
+         %13 = OpFunctionParameter %7
+         %15 = OpLabel
+         %37 = OpVariable %7 Function
+         %38 = OpVariable %7 Function
+         %39 = OpLoad %6 %13
+               OpStore %38 %39
+         %40 = OpFunctionCall %6 %10 %38
+               OpStore %37 %40
+         %41 = OpLoad %6 %37
+         %42 = OpLoad %6 %13
+         %43 = OpSGreaterThan %26 %41 %42
+               OpSelectionMerge %45 None
+               OpBranchConditional %43 %44 %45
+         %44 = OpLabel
+               OpStore %47 %48
+               OpBranch %45
+         %45 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Note: |env| should ideally be declared const.  However, due to a known
+  // issue with older versions of MSVC we would have to mark |env| as being
+  // captured due to its used in a lambda below, and other compilers would warn
+  // that such capturing is not necessary.  Not declaring |env| as const means
+  // that it needs to be captured to be used in the lambda, and thus all
+  // compilers are kept happy.  See:
+  // https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = kSilentConsumer;
+
+  SpirvTools tools(env);
+  std::vector<uint32_t> reference_binary;
+  ASSERT_TRUE(
+      tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
+
+  const auto variant_ir_context =
+      BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, variant_ir_context.get()));
+
+  const auto donor_ir_context =
+      BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_ir_context.get()));
+
+  PseudoRandomGenerator random_generator(0);
+  FuzzerContext fuzzer_context(&random_generator, 100);
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
+
+  protobufs::TransformationSequence transformations;
+  FuzzerPassDonateModules pass(variant_ir_context.get(),
+                               &transformation_context, &fuzzer_context,
+                               &transformations, {});
+  pass.DonateSingleModule(donor_ir_context.get(), true);
+
+  protobufs::FactSequence no_facts;
+
+  Shrinker::InterestingnessFunction interestingness_function =
+      [consumer, env](const std::vector<uint32_t>& binary,
+                      uint32_t /*unused*/) -> bool {
+    bool found_op_not = false;
+    uint32_t op_call_count = 0;
+    auto temp_ir_context =
+        BuildModule(env, consumer, binary.data(), binary.size());
+    for (auto& function : *temp_ir_context->module()) {
+      for (auto& block : function) {
+        for (auto& inst : block) {
+          if (inst.opcode() == SpvOpNot) {
+            found_op_not = true;
+          } else if (inst.opcode() == SpvOpFunctionCall) {
+            op_call_count++;
+          }
+        }
+      }
+    }
+    return found_op_not && op_call_count >= 2;
+  };
+
+  auto shrinker_result =
+      Shrinker(env, consumer, reference_binary, no_facts, transformations,
+               interestingness_function, 1000, true, validator_options)
+          .Run();
+  ASSERT_EQ(Shrinker::ShrinkerResultStatus::kComplete, shrinker_result.status);
+
+  // We now check that the module after shrinking looks right.
+  // The entry point should be identical to what it looked like in the
+  // reference, while the other functions should be absolutely minimal,
+  // containing only what is needed to satisfy the interestingness function.
+  auto ir_context_after_shrinking =
+      BuildModule(env, consumer, shrinker_result.transformed_binary.data(),
+                  shrinker_result.transformed_binary.size());
+  bool first_function = true;
+  for (auto& function : *ir_context_after_shrinking->module()) {
+    if (first_function) {
+      first_function = false;
+      bool first_block = true;
+      for (auto& block : function) {
+        ASSERT_TRUE(first_block);
+        uint32_t counter = 0;
+        for (auto& inst : block) {
+          switch (counter) {
+            case 0:
+              ASSERT_EQ(SpvOpVariable, inst.opcode());
+              ASSERT_EQ(11, inst.result_id());
+              break;
+            case 1:
+              ASSERT_EQ(SpvOpStore, inst.opcode());
+              break;
+            case 2:
+              ASSERT_EQ(SpvOpLoad, inst.opcode());
+              ASSERT_EQ(12, inst.result_id());
+              break;
+            case 3:
+              ASSERT_EQ(SpvOpStore, inst.opcode());
+              break;
+            case 4:
+              ASSERT_EQ(SpvOpReturn, inst.opcode());
+              break;
+            default:
+              FAIL();
+          }
+          counter++;
+        }
+      }
+    } else {
+      bool first_block = true;
+      for (auto& block : function) {
+        ASSERT_TRUE(first_block);
+        first_block = false;
+        for (auto& inst : block) {
+          switch (inst.opcode()) {
+            case SpvOpVariable:
+            case SpvOpNot:
+            case SpvOpReturn:
+            case SpvOpReturnValue:
+            case SpvOpFunctionCall:
+              // These are the only instructions we expect to see.
+              break;
+            default:
+              FAIL();
+          }
+        }
+      }
+    }
+  }
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools