reduce: improve remove unref instr pass (#2945)

* Remove Impl struct in Reducer; we can re-add it later (in a cleaner fashion) if we need to. 
* Add cleanup passes in Reducer; needed so that removal of constants can be disabled during the main passes, and then enabled during cleanup passes, otherwise some main passes can perform worse due to lack of available constants. 
* Delete passes: remove op name, remove relaxed precision. And delete associated tests. 
* Add more tests for remove unreferenced instructions. 
* Always return and write the output file, even if there was a reduction failure. 
* Only exit with 0 if the reduction completed or we hit the reduction step limit. 
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 35acf3f..00909ee 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -29,8 +29,6 @@
         remove_instruction_reduction_opportunity.h
         remove_function_reduction_opportunity.h
         remove_function_reduction_opportunity_finder.h
-        remove_opname_instruction_reduction_opportunity_finder.h
-        remove_relaxed_precision_decoration_opportunity_finder.h
         remove_selection_reduction_opportunity.h
         remove_selection_reduction_opportunity_finder.h
         remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -57,11 +55,9 @@
         remove_function_reduction_opportunity.cpp
         remove_function_reduction_opportunity_finder.cpp
         remove_instruction_reduction_opportunity.cpp
-        remove_relaxed_precision_decoration_opportunity_finder.cpp
         remove_selection_reduction_opportunity.cpp
         remove_selection_reduction_opportunity_finder.cpp
         remove_unreferenced_instruction_reduction_opportunity_finder.cpp
-        remove_opname_instruction_reduction_opportunity_finder.cpp
         structured_loop_to_selection_reduction_opportunity.cpp
         structured_loop_to_selection_reduction_opportunity_finder.cpp
         conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index ebb5d47..bda41ce 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -24,8 +24,6 @@
 #include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
 #include "source/reduce/remove_block_reduction_opportunity_finder.h"
 #include "source/reduce/remove_function_reduction_opportunity_finder.h"
-#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
-#include "source/reduce/remove_relaxed_precision_decoration_opportunity_finder.h"
 #include "source/reduce/remove_selection_reduction_opportunity_finder.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
@@ -35,41 +33,32 @@
 namespace spvtools {
 namespace reduce {
 
-struct Reducer::Impl {
-  explicit Impl(spv_target_env env) : target_env(env) {}
-
-  bool ReachedStepLimit(uint32_t current_step,
-                        spv_const_reducer_options options);
-
-  const spv_target_env target_env;  // Target environment.
-  MessageConsumer consumer;         // Message consumer.
-  InterestingnessFunction interestingness_function;
-  std::vector<std::unique_ptr<ReductionPass>> passes;
-};
-
-Reducer::Reducer(spv_target_env env) : impl_(MakeUnique<Impl>(env)) {}
+Reducer::Reducer(spv_target_env target_env) : target_env_(target_env) {}
 
 Reducer::~Reducer() = default;
 
 void Reducer::SetMessageConsumer(MessageConsumer c) {
-  for (auto& pass : impl_->passes) {
+  for (auto& pass : passes_) {
     pass->SetMessageConsumer(c);
   }
-  impl_->consumer = std::move(c);
+  for (auto& pass : cleanup_passes_) {
+    pass->SetMessageConsumer(c);
+  }
+  consumer_ = std::move(c);
 }
 
 void Reducer::SetInterestingnessFunction(
     Reducer::InterestingnessFunction interestingness_function) {
-  impl_->interestingness_function = std::move(interestingness_function);
+  interestingness_function_ = std::move(interestingness_function);
 }
 
 Reducer::ReductionResultStatus Reducer::Run(
     std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
     spv_const_reducer_options options,
-    spv_validator_options validator_options) const {
+    spv_validator_options validator_options) {
   std::vector<uint32_t> current_binary(std::move(binary_in));
 
-  spvtools::SpirvTools tools(impl_->target_env);
+  spvtools::SpirvTools tools(target_env_);
   assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
 
   // Keeps track of how many reduction attempts have been tried.  Reduction
@@ -79,105 +68,43 @@
   // Initial state should be valid.
   if (!tools.Validate(&current_binary[0], current_binary.size(),
                       validator_options)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is invalid; stopping.");
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is invalid; stopping.");
     return Reducer::ReductionResultStatus::kInitialStateInvalid;
   }
 
   // Initial state should be interesting.
-  if (!impl_->interestingness_function(current_binary, reductions_applied)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial state was not interesting; stopping.");
+  if (!interestingness_function_(current_binary, reductions_applied)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial state was not interesting; stopping.");
     return Reducer::ReductionResultStatus::kInitialStateNotInteresting;
   }
 
-  // Determines whether, on completing one round of reduction passes, it is
-  // worthwhile trying a further round.
-  bool another_round_worthwhile = true;
+  Reducer::ReductionResultStatus result =
+      RunPasses(&passes_, options, validator_options, tools, &current_binary,
+                &reductions_applied);
 
-  // Apply round after round of reduction passes until we hit the reduction
-  // step limit, or deem that another round is not going to be worthwhile.
-  while (!impl_->ReachedStepLimit(reductions_applied, options) &&
-         another_round_worthwhile) {
-    // At the start of a round of reduction passes, assume another round will
-    // not be worthwhile unless we find evidence to the contrary.
-    another_round_worthwhile = false;
-
-    // Iterate through the available passes
-    for (auto& pass : impl_->passes) {
-      // If this pass hasn't reached its minimum granularity then it's
-      // worth eventually doing another round of reductions, in order to
-      // try this pass at a finer granularity.
-      another_round_worthwhile |= !pass->ReachedMinimumGranularity();
-
-      // Keep applying this pass at its current granularity until it stops
-      // working or we hit the reduction step limit.
-      impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                      ("Trying pass " + pass->GetName() + ".").c_str());
-      do {
-        auto maybe_result = pass->TryApplyReduction(current_binary);
-        if (maybe_result.empty()) {
-          // For this round, the pass has no more opportunities (chunks) to
-          // apply, so move on to the next pass.
-          impl_->consumer(
-              SPV_MSG_INFO, nullptr, {},
-              ("Pass " + pass->GetName() + " did not make a reduction step.")
-                  .c_str());
-          break;
-        }
-        bool interesting = false;
-        std::stringstream stringstream;
-        reductions_applied++;
-        stringstream << "Pass " << pass->GetName() << " made reduction step "
-                     << reductions_applied << ".";
-        impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                        (stringstream.str().c_str()));
-        if (!tools.Validate(&maybe_result[0], maybe_result.size(),
-                            validator_options)) {
-          // The reduction step went wrong and an invalid binary was produced.
-          // By design, this shouldn't happen; this is a safeguard to stop an
-          // invalid binary from being regarded as interesting.
-          impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                          "Reduction step produced an invalid binary.");
-          if (options->fail_on_validation_error) {
-            return Reducer::ReductionResultStatus::kStateInvalid;
-          }
-        } else if (impl_->interestingness_function(maybe_result,
-                                                   reductions_applied)) {
-          // Success!  The binary produced by this reduction step is
-          // interesting, so make it the binary of interest henceforth, and
-          // note that it's worth doing another round of reduction passes.
-          impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                          "Reduction step succeeded.");
-          current_binary = std::move(maybe_result);
-          interesting = true;
-          another_round_worthwhile = true;
-        }
-        // We must call this before the next call to TryApplyReduction.
-        pass->NotifyInteresting(interesting);
-        // Bail out if the reduction step limit has been reached.
-      } while (!impl_->ReachedStepLimit(reductions_applied, options));
-    }
+  if (result == Reducer::ReductionResultStatus::kComplete) {
+    // Cleanup passes.
+    result = RunPasses(&cleanup_passes_, options, validator_options, tools,
+                       &current_binary, &reductions_applied);
   }
 
+  if (result == Reducer::ReductionResultStatus::kComplete) {
+    consumer_(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
+  }
+
+  // Even if the reduction has failed by this point (e.g. due to producing an
+  // invalid binary), we still update the output binary for better debugging.
   *binary_out = std::move(current_binary);
 
-  // Report whether reduction completed, or bailed out early due to reaching
-  // the step limit.
-  if (impl_->ReachedStepLimit(reductions_applied, options)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Reached reduction step limit; stopping.");
-    return Reducer::ReductionResultStatus::kReachedStepLimit;
-  }
-  impl_->consumer(SPV_MSG_INFO, nullptr, {}, "No more to reduce; stopping.");
-  return Reducer::ReductionResultStatus::kComplete;
+  return result;
 }
 
 void Reducer::AddDefaultReductionPasses() {
-  AddReductionPass(spvtools::MakeUnique<
-                   RemoveOpNameInstructionReductionOpportunityFinder>());
-  AddReductionPass(spvtools::MakeUnique<
-                   RemoveRelaxedPrecisionDecorationOpportunityFinder>());
+  AddReductionPass(
+      spvtools::MakeUnique<
+          RemoveUnreferencedInstructionReductionOpportunityFinder>(false));
   AddReductionPass(
       spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
   AddReductionPass(
@@ -185,8 +112,6 @@
   AddReductionPass(
       spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
   AddReductionPass(spvtools::MakeUnique<
-                   RemoveUnreferencedInstructionReductionOpportunityFinder>());
-  AddReductionPass(spvtools::MakeUnique<
                    StructuredLoopToSelectionReductionOpportunityFinder>());
   AddReductionPass(
       spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
@@ -201,18 +126,117 @@
           ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
   AddReductionPass(
       spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
+
+  // Cleanup passes.
+
+  AddCleanupReductionPass(
+      spvtools::MakeUnique<
+          RemoveUnreferencedInstructionReductionOpportunityFinder>(true));
 }
 
 void Reducer::AddReductionPass(
     std::unique_ptr<ReductionOpportunityFinder>&& finder) {
-  impl_->passes.push_back(spvtools::MakeUnique<ReductionPass>(
-      impl_->target_env, std::move(finder)));
+  passes_.push_back(
+      spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
 }
 
-bool Reducer::Impl::ReachedStepLimit(uint32_t current_step,
-                                     spv_const_reducer_options options) {
+void Reducer::AddCleanupReductionPass(
+    std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+  cleanup_passes_.push_back(
+      spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
+}
+
+bool Reducer::ReachedStepLimit(uint32_t current_step,
+                               spv_const_reducer_options options) {
   return current_step >= options->step_limit;
 }
 
+Reducer::ReductionResultStatus Reducer::RunPasses(
+    std::vector<std::unique_ptr<ReductionPass>>* passes,
+    spv_const_reducer_options options, spv_validator_options validator_options,
+    const SpirvTools& tools, std::vector<uint32_t>* current_binary,
+    uint32_t* const reductions_applied) {
+  // Determines whether, on completing one round of reduction passes, it is
+  // worthwhile trying a further round.
+  bool another_round_worthwhile = true;
+
+  // Apply round after round of reduction passes until we hit the reduction
+  // step limit, or deem that another round is not going to be worthwhile.
+  while (!ReachedStepLimit(*reductions_applied, options) &&
+         another_round_worthwhile) {
+    // At the start of a round of reduction passes, assume another round will
+    // not be worthwhile unless we find evidence to the contrary.
+    another_round_worthwhile = false;
+
+    // Iterate through the available passes.
+    for (auto& pass : *passes) {
+      // If this pass hasn't reached its minimum granularity then it's
+      // worth eventually doing another round of reductions, in order to
+      // try this pass at a finer granularity.
+      another_round_worthwhile |= !pass->ReachedMinimumGranularity();
+
+      // Keep applying this pass at its current granularity until it stops
+      // working or we hit the reduction step limit.
+      consumer_(SPV_MSG_INFO, nullptr, {},
+                ("Trying pass " + pass->GetName() + ".").c_str());
+      do {
+        auto maybe_result = pass->TryApplyReduction(*current_binary);
+        if (maybe_result.empty()) {
+          // For this round, the pass has no more opportunities (chunks) to
+          // apply, so move on to the next pass.
+          consumer_(
+              SPV_MSG_INFO, nullptr, {},
+              ("Pass " + pass->GetName() + " did not make a reduction step.")
+                  .c_str());
+          break;
+        }
+        bool interesting = false;
+        std::stringstream stringstream;
+        (*reductions_applied)++;
+        stringstream << "Pass " << pass->GetName() << " made reduction step "
+                     << *reductions_applied << ".";
+        consumer_(SPV_MSG_INFO, nullptr, {}, (stringstream.str().c_str()));
+        if (!tools.Validate(&maybe_result[0], maybe_result.size(),
+                            validator_options)) {
+          // The reduction step went wrong and an invalid binary was produced.
+          // By design, this shouldn't happen; this is a safeguard to stop an
+          // invalid binary from being regarded as interesting.
+          consumer_(SPV_MSG_INFO, nullptr, {},
+                    "Reduction step produced an invalid binary.");
+          if (options->fail_on_validation_error) {
+            // In this mode, we fail, so we update the current binary so it is
+            // output for debugging.
+            *current_binary = std::move(maybe_result);
+            return Reducer::ReductionResultStatus::kStateInvalid;
+          }
+        } else if (interestingness_function_(maybe_result,
+                                             *reductions_applied)) {
+          // Success!  The binary produced by this reduction step is
+          // interesting, so make it the binary of interest henceforth, and
+          // note that it's worth doing another round of reduction passes.
+          consumer_(SPV_MSG_INFO, nullptr, {}, "Reduction step succeeded.");
+          *current_binary = std::move(maybe_result);
+          interesting = true;
+          another_round_worthwhile = true;
+        }
+        // We must call this before the next call to TryApplyReduction.
+        pass->NotifyInteresting(interesting);
+        // Bail out if the reduction step limit has been reached.
+      } while (!ReachedStepLimit(*reductions_applied, options));
+    }
+  }
+
+  // Report whether reduction completed, or bailed out early due to reaching
+  // the step limit.
+  if (ReachedStepLimit(*reductions_applied, options)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Reached reduction step limit; stopping.");
+    return Reducer::ReductionResultStatus::kReachedStepLimit;
+  }
+
+  // The passes completed successfully, although we may still run more passes.
+  return Reducer::ReductionResultStatus::kComplete;
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
index a9b28c3..864ce75 100644
--- a/source/reduce/reducer.h
+++ b/source/reduce/reducer.h
@@ -50,7 +50,7 @@
   using InterestingnessFunction =
       std::function<bool(const std::vector<uint32_t>&, uint32_t)>;
 
-  // Constructs an instance with the given target |env|, which is used to
+  // Constructs an instance with the given target |target_env|, which is used to
   // decode the binary to be reduced later.
   //
   // The constructed instance will have an empty message consumer, which just
@@ -59,7 +59,7 @@
   //
   // The constructed instance also needs to have an interestingness function
   // set and some reduction passes added to it in order to be useful.
-  explicit Reducer(spv_target_env env);
+  explicit Reducer(spv_target_env target_env);
 
   // Disables copy/move constructor/assignment operations.
   Reducer(const Reducer&) = delete;
@@ -86,17 +86,34 @@
   // that will be iterated over.
   void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder>&& finder);
 
+  // Adds a cleanup reduction pass based on the given finder to the sequence of
+  // passes that will run after other passes.
+  void AddCleanupReductionPass(
+      std::unique_ptr<ReductionOpportunityFinder>&& finder);
+
   // Reduces the given SPIR-V module |binary_out|.
   // The reduced binary ends up in |binary_out|.
   // A status is returned.
   ReductionResultStatus Run(std::vector<uint32_t>&& binary_in,
                             std::vector<uint32_t>* binary_out,
                             spv_const_reducer_options options,
-                            spv_validator_options validator_options) const;
+                            spv_validator_options validator_options);
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  static bool ReachedStepLimit(uint32_t current_step,
+                               spv_const_reducer_options options);
+
+  ReductionResultStatus RunPasses(
+      std::vector<std::unique_ptr<ReductionPass>>* passes,
+      spv_const_reducer_options options,
+      spv_validator_options validator_options, const SpirvTools& tools,
+      std::vector<uint32_t>* current_binary, uint32_t* reductions_applied);
+
+  const spv_target_env target_env_;
+  MessageConsumer consumer_;
+  InterestingnessFunction interestingness_function_;
+  std::vector<std::unique_ptr<ReductionPass>> passes_;
+  std::vector<std::unique_ptr<ReductionPass>> cleanup_passes_;
 };
 
 }  // namespace reduce
diff --git a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
deleted file mode 100644
index f687d71..0000000
--- a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// 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/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
-
-#include "source/opcode.h"
-#include "source/opt/instruction.h"
-#include "source/reduce/remove_instruction_reduction_opportunity.h"
-
-namespace spvtools {
-namespace reduce {
-
-using opt::IRContext;
-
-std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveOpNameInstructionReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
-  std::vector<std::unique_ptr<ReductionOpportunity>> result;
-
-  for (auto& inst : context->module()->debugs2()) {
-    if (inst.opcode() == SpvOpName || inst.opcode() == SpvOpMemberName) {
-      result.push_back(
-          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-    }
-  }
-  return result;
-}
-
-std::string RemoveOpNameInstructionReductionOpportunityFinder::GetName() const {
-  return "RemoveOpNameInstructionReductionOpportunityFinder";
-}
-
-}  // namespace reduce
-}  // namespace spvtools
diff --git a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h b/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
deleted file mode 100644
index 8b9fd6f..0000000
--- a/source/reduce/remove_opname_instruction_reduction_opportunity_finder.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (c) 2018 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_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
-#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
-
-#include "source/reduce/reduction_opportunity_finder.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A finder for opportunities to remove OpName instructions.  As well as making
-// the module smaller, removing an OpName instruction may create opportunities
-// for subsequently removing the instructions that create the ids to which the
-// OpName applies.
-class RemoveOpNameInstructionReductionOpportunityFinder
-    : public ReductionOpportunityFinder {
- public:
-  RemoveOpNameInstructionReductionOpportunityFinder() = default;
-
-  ~RemoveOpNameInstructionReductionOpportunityFinder() override = default;
-
-  std::string GetName() const final;
-
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_relaxed_precision_decoration_opportunity_finder.cpp b/source/reduce/remove_relaxed_precision_decoration_opportunity_finder.cpp
deleted file mode 100644
index 352cefb..0000000
--- a/source/reduce/remove_relaxed_precision_decoration_opportunity_finder.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) 2018 Google Inc.
-//
-// 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/reduce/remove_relaxed_precision_decoration_opportunity_finder.h"
-
-#include "source/reduce/remove_instruction_reduction_opportunity.h"
-
-namespace spvtools {
-namespace reduce {
-
-std::vector<std::unique_ptr<ReductionOpportunity>>
-RemoveRelaxedPrecisionDecorationOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
-  std::vector<std::unique_ptr<ReductionOpportunity>> result;
-
-  // Consider all annotation instructions
-  for (auto& inst : context->module()->annotations()) {
-    // We are interested in removing instructions of the form:
-    //   SpvOpDecorate %id RelaxedPrecision
-    // and
-    //   SpvOpMemberDecorate %id member RelaxedPrecision
-    if ((inst.opcode() == SpvOpDecorate &&
-         inst.GetSingleWordInOperand(1) == SpvDecorationRelaxedPrecision) ||
-        (inst.opcode() == SpvOpMemberDecorate &&
-         inst.GetSingleWordInOperand(2) == SpvDecorationRelaxedPrecision)) {
-      result.push_back(
-          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-    }
-  }
-  return result;
-}
-
-std::string RemoveRelaxedPrecisionDecorationOpportunityFinder::GetName() const {
-  return "RemoveRelaxedPrecisionDecorationOpportunityFinder";
-}
-
-}  // namespace reduce
-}  // namespace spvtools
diff --git a/source/reduce/remove_relaxed_precision_decoration_opportunity_finder.h b/source/reduce/remove_relaxed_precision_decoration_opportunity_finder.h
deleted file mode 100644
index 673049c..0000000
--- a/source/reduce/remove_relaxed_precision_decoration_opportunity_finder.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (c) 2019 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_REDUCE_REMOVE_RELAXED_PRECISION_OPPORTUNITY_FINDER_H_
-#define SOURCE_REDUCE_REMOVE_RELAXED_PRECISION_OPPORTUNITY_FINDER_H_
-
-#include "source/reduce/reduction_opportunity_finder.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A finder for opportunities to remove relaxed precision decorations.
-class RemoveRelaxedPrecisionDecorationOpportunityFinder
-    : public ReductionOpportunityFinder {
- public:
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const override;
-
-  std::string GetName() const override;
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_REMOVE_RELAXED_PRECISION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
index dabee50..da61c8d 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
@@ -21,28 +21,109 @@
 namespace spvtools {
 namespace reduce {
 
+RemoveUnreferencedInstructionReductionOpportunityFinder::
+    RemoveUnreferencedInstructionReductionOpportunityFinder(
+        bool remove_constants_and_undefs)
+    : remove_constants_and_undefs_(remove_constants_and_undefs) {}
+
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveUnreferencedInstructionReductionOpportunityFinder::
     GetAvailableOpportunities(opt::IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
+  for (auto& inst : context->module()->debugs1()) {
+    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+      continue;
+    }
+    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+  }
+
+  for (auto& inst : context->module()->debugs2()) {
+    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+      continue;
+    }
+    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+  }
+
+  for (auto& inst : context->module()->debugs3()) {
+    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+      continue;
+    }
+    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+  }
+
+  for (auto& inst : context->module()->types_values()) {
+    if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
+      continue;
+    }
+    if (!remove_constants_and_undefs_ &&
+        spvOpcodeIsConstantOrUndef(inst.opcode())) {
+      continue;
+    }
+    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+  }
+
+  for (auto& inst : context->module()->annotations()) {
+    if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
+      continue;
+    }
+
+    uint32_t decoration = SpvDecorationMax;
+    switch (inst.opcode()) {
+      case SpvOpDecorate:
+      case SpvOpDecorateId:
+      case SpvOpDecorateString:
+        decoration = inst.GetSingleWordInOperand(1u);
+        break;
+      case SpvOpMemberDecorate:
+      case SpvOpMemberDecorateString:
+        decoration = inst.GetSingleWordInOperand(2u);
+        break;
+      default:
+        break;
+    }
+
+    // We conservatively only remove specific decorations that we believe will
+    // not change the shader interface, will not make the shader invalid, will
+    // actually be found in practice, etc.
+
+    switch (decoration) {
+      case SpvDecorationRelaxedPrecision:
+      case SpvDecorationNoSignedWrap:
+      case SpvDecorationNoContraction:
+      case SpvDecorationNoUnsignedWrap:
+      case SpvDecorationUserSemantic:
+        break;
+      default:
+        // Give up.
+        continue;
+    }
+
+    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+  }
+
   for (auto& function : *context->module()) {
     for (auto& block : function) {
       for (auto& inst : block) {
         if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
           continue;
         }
+        if (!remove_constants_and_undefs_ &&
+            spvOpcodeIsConstantOrUndef(inst.opcode())) {
+          continue;
+        }
         if (spvOpcodeIsBlockTerminator(inst.opcode()) ||
             inst.opcode() == SpvOpSelectionMerge ||
             inst.opcode() == SpvOpLoopMerge) {
-          // In this reduction pass we do not want to affect static control
-          // flow.
+          // In this reduction pass we do not want to affect static
+          // control flow.
           continue;
         }
-        // Given that we're in a block, we should only get here if the
-        // instruction is not directly related to control flow; i.e., it's
-        // some straightforward instruction with an unused result, like an
-        // arithmetic operation or function call.
+        // Given that we're in a block, we should only get here if
+        // the instruction is not directly related to control flow;
+        // i.e., it's some straightforward instruction with an
+        // unused result, like an arithmetic operation or function
+        // call.
         result.push_back(
             MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
       }
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
index 30f460b..bc4f137 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
+++ b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
@@ -28,7 +28,8 @@
 class RemoveUnreferencedInstructionReductionOpportunityFinder
     : public ReductionOpportunityFinder {
  public:
-  RemoveUnreferencedInstructionReductionOpportunityFinder() = default;
+  explicit RemoveUnreferencedInstructionReductionOpportunityFinder(
+      bool remove_constants_and_undefs);
 
   ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
 
@@ -38,6 +39,7 @@
       opt::IRContext* context) const final;
 
  private:
+  bool remove_constants_and_undefs_;
 };
 
 }  // namespace reduce
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index 2d3b378..b19bba4 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -23,8 +23,6 @@
         reducer_test.cpp
         remove_block_test.cpp
         remove_function_test.cpp
-        remove_opname_instruction_test.cpp
-        remove_relaxed_precision_decoration_test.cpp
         remove_selection_test.cpp
         remove_unreferenced_instruction_test.cpp
         structured_loop_to_selection_test.cpp
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 8787733..189e922 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -14,8 +14,8 @@
 
 #include "source/reduce/reducer.h"
 
+#include "source/opt/build_module.h"
 #include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
-#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
 #include "test/reduce/reduce_test_util.h"
 
@@ -23,6 +23,12 @@
 namespace reduce {
 namespace {
 
+using opt::BasicBlock;
+using opt::IRContext;
+
+const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
+const MessageConsumer kMessageConsumer = CLIMessageConsumer;
+
 // This changes its mind each time IsInteresting is invoked as to whether the
 // binary is interesting, until some limit is reached after which the binary is
 // always deemed interesting.  This is useful to test that reduction passes
@@ -55,6 +61,8 @@
 };
 
 TEST(ReducerTest, ExprToConstantAndRemoveUnreferenced) {
+  // Check that ExprToConstant and RemoveUnreferenced work together; once some
+  // ID uses have been changed to constants, those IDs can be removed.
   std::string original = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -149,15 +157,6 @@
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %60
                OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %16 "buf2"
-               OpMemberName %16 0 "i"
-               OpName %18 ""
-               OpName %25 "buf1"
-               OpMemberName %25 0 "f"
-               OpName %27 ""
-               OpName %60 "_GLF_color"
                OpMemberDecorate %16 0 Offset 0
                OpDecorate %16 Block
                OpDecorate %18 DescriptorSet 0
@@ -174,14 +173,12 @@
          %16 = OpTypeStruct %6
          %17 = OpTypePointer Uniform %16
          %18 = OpVariable %17 Uniform
-         %19 = OpTypePointer Uniform %6
          %22 = OpTypeBool
         %100 = OpConstantTrue %22
          %24 = OpTypeFloat 32
          %25 = OpTypeStruct %24
          %26 = OpTypePointer Uniform %25
          %27 = OpVariable %26 Uniform
-         %28 = OpTypePointer Uniform %24
          %31 = OpConstant %24 2
          %56 = OpConstant %6 1
          %58 = OpTypeVector %24 4
@@ -209,8 +206,7 @@
                OpFunctionEnd
   )";
 
-  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
-  Reducer reducer(env);
+  Reducer reducer(kEnv);
   PingPongInteresting ping_pong_interesting(10);
   reducer.SetMessageConsumer(NopDiagnostic);
   reducer.SetInterestingnessFunction(
@@ -218,12 +214,13 @@
         return ping_pong_interesting.IsInteresting(binary);
       });
   reducer.AddReductionPass(
+      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>(
+          false));
+  reducer.AddReductionPass(
       MakeUnique<OperandToConstReductionOpportunityFinder>());
-  reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
 
   std::vector<uint32_t> binary_in;
-  SpirvTools t(env);
+  SpirvTools t(kEnv);
 
   ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
   std::vector<uint32_t> binary_out;
@@ -237,83 +234,7 @@
 
   ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 
-  CheckEqual(env, expected, binary_out);
-}
-
-TEST(ReducerTest, RemoveOpnameAndRemoveUnreferenced) {
-  const std::string original = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %2 "main"
-               OpExecutionMode %2 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %2 "main"
-               OpName %3 "a"
-               OpName %4 "this-name-counts-as-usage-for-load-instruction"
-          %5 = OpTypeVoid
-          %6 = OpTypeFunction %5
-          %7 = OpTypeFloat 32
-          %8 = OpTypePointer Function %7
-          %9 = OpConstant %7 1
-          %2 = OpFunction %5 None %6
-         %10 = OpLabel
-          %3 = OpVariable %8 Function
-          %4 = OpLoad %7 %3
-               OpStore %3 %9
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string expected = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %2 "main"
-               OpExecutionMode %2 OriginUpperLeft
-               OpSource ESSL 310
-          %5 = OpTypeVoid
-          %6 = OpTypeFunction %5
-          %7 = OpTypeFloat 32
-          %8 = OpTypePointer Function %7
-          %9 = OpConstant %7 1
-          %2 = OpFunction %5 None %6
-         %10 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
-  Reducer reducer(env);
-  // Make ping-pong interesting very quickly, as there are not many
-  // opportunities.
-  PingPongInteresting ping_pong_interesting(1);
-  reducer.SetMessageConsumer(NopDiagnostic);
-  reducer.SetInterestingnessFunction(
-      [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
-        return ping_pong_interesting.IsInteresting(binary);
-      });
-  reducer.AddReductionPass(
-      MakeUnique<RemoveOpNameInstructionReductionOpportunityFinder>());
-  reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>());
-
-  std::vector<uint32_t> binary_in;
-  SpirvTools t(env);
-
-  ASSERT_TRUE(t.Assemble(original, &binary_in, kReduceAssembleOption));
-  std::vector<uint32_t> binary_out;
-  spvtools::ReducerOptions reducer_options;
-  reducer_options.set_step_limit(500);
-  reducer_options.set_fail_on_validation_error(true);
-  spvtools::ValidatorOptions validator_options;
-
-  Reducer::ReductionResultStatus status = reducer.Run(
-      std::move(binary_in), &binary_out, reducer_options, validator_options);
-
-  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
-
-  CheckEqual(env, expected, binary_out);
+  CheckEqual(kEnv, expected, binary_out);
 }
 
 }  // namespace
diff --git a/test/reduce/remove_opname_instruction_test.cpp b/test/reduce/remove_opname_instruction_test.cpp
deleted file mode 100644
index 9d40cfc..0000000
--- a/test/reduce/remove_opname_instruction_test.cpp
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright (c) 2018 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/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
-
-#include "source/opt/build_module.h"
-#include "source/reduce/reduction_opportunity.h"
-#include "source/reduce/reduction_pass.h"
-#include "test/reduce/reduce_test_util.h"
-
-namespace spvtools {
-namespace reduce {
-namespace {
-
-TEST(RemoveOpnameInstructionReductionPassTest, NothingToRemove) {
-  const std::string source = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, source, kReduceAssembleOption);
-  const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
-  ASSERT_EQ(0, ops.size());
-}
-
-TEST(RemoveOpnameInstructionReductionPassTest, RemoveSingleOpName) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-  )";
-
-  const std::string epilogue = R"(
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpName %4 "main"
-  )" + epilogue;
-
-  const std::string expected = prologue + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
-  ASSERT_EQ(1, ops.size());
-  ASSERT_TRUE(ops[0]->PreconditionHolds());
-  ops[0]->TryToApply();
-
-  CheckEqual(env, expected, context.get());
-}
-
-TEST(RemoveOpnameInstructionReductionPassTest, TryApplyRemovesAllOpName) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-  )";
-
-  const std::string epilogue = R"(
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 1
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %10 = OpVariable %7 Function
-         %11 = OpVariable %7 Function
-         %12 = OpVariable %7 Function
-               OpStore %8 %9
-               OpStore %10 %9
-               OpStore %11 %9
-               OpStore %12 %9
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpName %4 "main"
-               OpName %8 "a"
-               OpName %10 "b"
-               OpName %11 "c"
-               OpName %12 "d"
-  )" + epilogue;
-
-  const std::string expected = prologue + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-
-  {
-    // Check the right number of opportunities is detected
-    const auto consumer = nullptr;
-    const auto context =
-        BuildModule(env, consumer, original, kReduceAssembleOption);
-    const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
-                         .GetAvailableOpportunities(context.get());
-    ASSERT_EQ(5, ops.size());
-  }
-
-  {
-    // The reduction should remove all OpName
-    std::vector<uint32_t> binary;
-    SpirvTools t(env);
-    ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-    auto reduced_binary =
-        ReductionPass(env,
-                      spvtools::MakeUnique<
-                          RemoveOpNameInstructionReductionOpportunityFinder>())
-            .TryApplyReduction(binary);
-    CheckEqual(env, expected, reduced_binary);
-  }
-}
-
-TEST(RemoveOpnameInstructionReductionPassTest,
-     TryApplyRemovesAllOpNameAndOpMemberName) {
-  const std::string prologue = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-  )";
-
-  const std::string epilogue = R"(
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeInt 32 1
-          %8 = OpTypeVector %6 3
-          %9 = OpTypeStruct %6 %7 %8
-         %10 = OpTypePointer Function %9
-         %12 = OpConstant %7 0
-         %13 = OpConstant %6 1
-         %14 = OpTypePointer Function %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-         %11 = OpVariable %10 Function
-         %15 = OpAccessChain %14 %11 %12
-               OpStore %15 %13
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const std::string original = prologue + R"(
-               OpName %4 "main"
-               OpName %9 "S"
-               OpMemberName %9 0 "f"
-               OpMemberName %9 1 "i"
-               OpMemberName %9 2 "v"
-               OpName %11 "s"
-  )" + epilogue;
-
-  const std::string expected = prologue + epilogue;
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-
-  {
-    // Check the right number of opportunities is detected
-    const auto consumer = nullptr;
-    const auto context =
-        BuildModule(env, consumer, original, kReduceAssembleOption);
-    const auto ops = RemoveOpNameInstructionReductionOpportunityFinder()
-                         .GetAvailableOpportunities(context.get());
-    ASSERT_EQ(6, ops.size());
-  }
-
-  {
-    // The reduction should remove all OpName
-    std::vector<uint32_t> binary;
-    SpirvTools t(env);
-    ASSERT_TRUE(t.Assemble(original, &binary, kReduceAssembleOption));
-    auto reduced_binary =
-        ReductionPass(env,
-                      spvtools::MakeUnique<
-                          RemoveOpNameInstructionReductionOpportunityFinder>())
-            .TryApplyReduction(binary);
-    CheckEqual(env, expected, reduced_binary);
-  }
-}
-
-}  // namespace
-}  // namespace reduce
-}  // namespace spvtools
diff --git a/test/reduce/remove_relaxed_precision_decoration_test.cpp b/test/reduce/remove_relaxed_precision_decoration_test.cpp
deleted file mode 100644
index f9ff081..0000000
--- a/test/reduce/remove_relaxed_precision_decoration_test.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) 2019 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/reduce/remove_relaxed_precision_decoration_opportunity_finder.h"
-
-#include "source/opt/build_module.h"
-#include "source/reduce/reduction_opportunity.h"
-#include "source/reduce/reduction_pass.h"
-#include "test/reduce/reduce_test_util.h"
-
-namespace spvtools {
-namespace reduce {
-namespace {
-
-TEST(RemoveRelaxedPrecisionDecorationTest, NothingToRemove) {
-  const std::string source = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, source, kReduceAssembleOption);
-  const auto ops = RemoveRelaxedPrecisionDecorationOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
-  ASSERT_EQ(0, ops.size());
-}
-
-TEST(RemoveRelaxedPrecisionDecorationTest, RemoveDecorations) {
-  const std::string source = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "f"
-               OpName %12 "i"
-               OpName %16 "v"
-               OpName %19 "S"
-               OpMemberName %19 0 "a"
-               OpMemberName %19 1 "b"
-               OpMemberName %19 2 "c"
-               OpName %21 "s"
-               OpDecorate %8 RelaxedPrecision
-               OpDecorate %12 RelaxedPrecision
-               OpDecorate %16 RelaxedPrecision
-               OpDecorate %17 RelaxedPrecision
-               OpDecorate %18 RelaxedPrecision
-               OpMemberDecorate %19 0 RelaxedPrecision
-               OpMemberDecorate %19 1 RelaxedPrecision
-               OpMemberDecorate %19 2 RelaxedPrecision
-               OpDecorate %22 RelaxedPrecision
-               OpDecorate %23 RelaxedPrecision
-               OpDecorate %24 RelaxedPrecision
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 2
-         %10 = OpTypeInt 32 1
-         %11 = OpTypePointer Function %10
-         %13 = OpConstant %10 22
-         %14 = OpTypeVector %6 2
-         %15 = OpTypePointer Function %14
-         %19 = OpTypeStruct %10 %6 %14
-         %20 = OpTypePointer Function %19
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %12 = OpVariable %11 Function
-         %16 = OpVariable %15 Function
-         %21 = OpVariable %20 Function
-               OpStore %8 %9
-               OpStore %12 %13
-         %17 = OpLoad %6 %8
-         %18 = OpCompositeConstruct %14 %17 %17
-               OpStore %16 %18
-         %22 = OpLoad %10 %12
-         %23 = OpLoad %6 %8
-         %24 = OpLoad %14 %16
-         %25 = OpCompositeConstruct %19 %22 %23 %24
-               OpStore %21 %25
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, source, kReduceAssembleOption);
-  const auto ops = RemoveRelaxedPrecisionDecorationOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
-  ASSERT_EQ(11, ops.size());
-
-  for (auto& op : ops) {
-    ASSERT_TRUE(op->PreconditionHolds());
-    op->TryToApply();
-  }
-
-  const std::string expected = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "f"
-               OpName %12 "i"
-               OpName %16 "v"
-               OpName %19 "S"
-               OpMemberName %19 0 "a"
-               OpMemberName %19 1 "b"
-               OpMemberName %19 2 "c"
-               OpName %21 "s"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 2
-         %10 = OpTypeInt 32 1
-         %11 = OpTypePointer Function %10
-         %13 = OpConstant %10 22
-         %14 = OpTypeVector %6 2
-         %15 = OpTypePointer Function %14
-         %19 = OpTypeStruct %10 %6 %14
-         %20 = OpTypePointer Function %19
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %12 = OpVariable %11 Function
-         %16 = OpVariable %15 Function
-         %21 = OpVariable %20 Function
-               OpStore %8 %9
-               OpStore %12 %13
-         %17 = OpLoad %6 %8
-         %18 = OpCompositeConstruct %14 %17 %17
-               OpStore %16 %18
-         %22 = OpLoad %10 %12
-         %23 = OpLoad %6 %8
-         %24 = OpLoad %14 %16
-         %25 = OpCompositeConstruct %19 %22 %23 %24
-               OpStore %21 %25
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  CheckEqual(env, expected, context.get());
-}
-
-}  // namespace
-}  // namespace reduce
-}  // namespace spvtools
diff --git a/test/reduce/remove_unreferenced_instruction_test.cpp b/test/reduce/remove_unreferenced_instruction_test.cpp
index 0babf78..3caf88c 100644
--- a/test/reduce/remove_unreferenced_instruction_test.cpp
+++ b/test/reduce/remove_unreferenced_instruction_test.cpp
@@ -23,19 +23,26 @@
 namespace reduce {
 namespace {
 
+const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
+
 TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
-  const std::string prologue = R"(
+  // A module with some unused instructions, including some unused OpStore
+  // instructions.
+
+  RemoveUnreferencedInstructionReductionOpportunityFinder finder(true);
+
+  const std::string original = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
                OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "a"
-               OpName %10 "b"
-               OpName %12 "c"
-               OpName %14 "d"
+               OpSource ESSL 310  ; 0
+               OpName %4 "main"   ; 1
+               OpName %8 "a"      ; 2
+               OpName %10 "b"     ; 3
+               OpName %12 "c"     ; 4
+               OpName %14 "d"     ; 5
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeInt 32 1
@@ -49,51 +56,323 @@
          %10 = OpVariable %7 Function
          %12 = OpVariable %7 Function
          %14 = OpVariable %7 Function
+               OpStore %8 %9           ; 6
+               OpStore %10 %11         ; 7
+               OpStore %12 %13         ; 8
+         %15 = OpLoad %6 %8
+               OpStore %14 %15         ; 9
+               OpReturn
+               OpFunctionEnd
+
   )";
 
-  const std::string epilogue = R"(
+  const MessageConsumer consumer = nullptr;
+  const auto context =
+      BuildModule(kEnv, consumer, original, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(10, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  const std::string step_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10       ; 0
+         %11 = OpConstant %6 20       ; 1
+         %13 = OpConstant %6 30       ; 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function ; 3
+         %12 = OpVariable %7 Function ; 4
+         %14 = OpVariable %7 Function ; 5
+         %15 = OpLoad %6 %8           ; 6
                OpReturn
                OpFunctionEnd
   )";
 
-  const std::string original = prologue + R"(
-               OpStore %8 %9
-               OpStore %10 %11
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
+  CheckEqual(kEnv, step_2, context.get());
 
-  const std::string expected_after_2 = prologue + R"(
-               OpStore %12 %13
-         %15 = OpLoad %6 %8
-               OpStore %14 %15
-  )" + epilogue;
+  ops = finder.GetAvailableOpportunities(context.get());
 
-  const std::string expected_after_4 = prologue + R"(
-         %15 = OpLoad %6 %8
-  )" + epilogue;
+  ASSERT_EQ(7, ops.size());
 
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context =
-      BuildModule(env, consumer, original, kReduceAssembleOption);
-  const auto ops = RemoveUnreferencedInstructionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
-  ASSERT_EQ(4, ops.size());
-  ASSERT_TRUE(ops[0]->PreconditionHolds());
-  ops[0]->TryToApply();
-  ASSERT_TRUE(ops[1]->PreconditionHolds());
-  ops[1]->TryToApply();
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
 
-  CheckEqual(env, expected_after_2, context.get());
+  const std::string step_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function   ; 0
+               OpReturn
+               OpFunctionEnd
+  )";
 
-  ASSERT_TRUE(ops[2]->PreconditionHolds());
-  ops[2]->TryToApply();
-  ASSERT_TRUE(ops[3]->PreconditionHolds());
-  ops[3]->TryToApply();
+  CheckEqual(kEnv, step_3, context.get());
 
-  CheckEqual(env, expected_after_4, context.get());
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  const std::string step_4 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6  ; 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(kEnv, step_4, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  const std::string step_5 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1        ; 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(kEnv, step_5, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  const std::string step_6 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(kEnv, step_6, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveUnreferencedInstructionReductionPassTest, Referenced) {
+  // A module with some unused global variables, constants, and types. Some will
+  // not be removed initially because of the OpDecorate instructions.
+
+  RemoveUnreferencedInstructionReductionOpportunityFinder finder(true);
+
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310                 ; 1
+               OpName %4 "main"                  ; 2
+               OpName %12 "a"                    ; 3
+               OpDecorate %12 RelaxedPrecision   ; 4
+               OpDecorate %13 RelaxedPrecision   ; 5
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6                 ; 6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Private %10
+         %12 = OpVariable %11 Private
+         %13 = OpConstant %10 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  auto context = BuildModule(kEnv, nullptr, shader, kReduceAssembleOption);
+
+  CheckValid(kEnv, context.get());
+
+  auto ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(6, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool                 ; 1
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Private %10
+         %12 = OpVariable %11 Private     ; 2
+         %13 = OpConstant %10 1           ; 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  CheckEqual(kEnv, after, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(3, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  std::string after_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1
+         %11 = OpTypePointer Private %10   ; 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  CheckEqual(kEnv, after_2, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  std::string after_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 1          ; 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  CheckEqual(kEnv, after_3, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+    CheckValid(kEnv, context.get());
+  }
+
+  std::string after_4 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  CheckEqual(kEnv, after_4, context.get());
+
+  ops = finder.GetAvailableOpportunities(context.get());
+
+  ASSERT_EQ(0, ops.size());
 }
 
 }  // namespace
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 930fa5d..0bdeb82 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -308,12 +308,20 @@
   const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
                                             reducer_options, validator_options);
 
-  if (reduction_status == spvtools::reduce::Reducer::ReductionResultStatus::
-                              kInitialStateNotInteresting ||
-      !WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
+  // Always try to write the output file, even if the reduction failed.
+  if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
                            binary_out.size())) {
     return 1;
   }
 
-  return 0;
+  // These are the only successful statuses.
+  switch (reduction_status) {
+    case spvtools::reduce::Reducer::ReductionResultStatus::kComplete:
+    case spvtools::reduce::Reducer::ReductionResultStatus::kReachedStepLimit:
+      return 0;
+    default:
+      break;
+  }
+
+  return 1;
 }