spirv-reduce: Remove unused uniforms and similar (#3321)

Extends the pass for removing unused instructions so that it can
remove global declarations (such as types and variables) that are only
used by decorations with which they are intimately connected, such as
descriptor set and binding decorations.
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 51e9b1d..b173ac0 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -26,12 +26,12 @@
         reduction_util.h
         remove_block_reduction_opportunity.h
         remove_block_reduction_opportunity_finder.h
-        remove_instruction_reduction_opportunity.h
         remove_function_reduction_opportunity.h
         remove_function_reduction_opportunity_finder.h
+        remove_instruction_reduction_opportunity.h
         remove_selection_reduction_opportunity.h
         remove_selection_reduction_opportunity_finder.h
-        remove_unreferenced_instruction_reduction_opportunity_finder.h
+        remove_unused_instruction_reduction_opportunity_finder.h
         structured_loop_to_selection_reduction_opportunity.h
         structured_loop_to_selection_reduction_opportunity_finder.h
         conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -57,7 +57,7 @@
         remove_instruction_reduction_opportunity.cpp
         remove_selection_reduction_opportunity.cpp
         remove_selection_reduction_opportunity_finder.cpp
-        remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+        remove_unused_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/pch_source_reduce.h b/source/reduce/pch_source_reduce.h
index 6c0da0c..81bed20 100644
--- a/source/reduce/pch_source_reduce.h
+++ b/source/reduce/pch_source_reduce.h
@@ -20,4 +20,4 @@
 #include "source/reduce/reduction_opportunity.h"
 #include "source/reduce/reduction_pass.h"
 #include "source/reduce/remove_instruction_reduction_opportunity.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index bda41ce..0179382 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -25,7 +25,7 @@
 #include "source/reduce/remove_block_reduction_opportunity_finder.h"
 #include "source/reduce/remove_function_reduction_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/remove_unused_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
 #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
 #include "source/spirv_reducer_options.h"
@@ -103,8 +103,8 @@
 
 void Reducer::AddDefaultReductionPasses() {
   AddReductionPass(
-      spvtools::MakeUnique<
-          RemoveUnreferencedInstructionReductionOpportunityFinder>(false));
+      spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(
+          false));
   AddReductionPass(
       spvtools::MakeUnique<OperandToUndefReductionOpportunityFinder>());
   AddReductionPass(
@@ -130,8 +130,8 @@
   // Cleanup passes.
 
   AddCleanupReductionPass(
-      spvtools::MakeUnique<
-          RemoveUnreferencedInstructionReductionOpportunityFinder>(true));
+      spvtools::MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(
+          true));
 }
 
 void Reducer::AddReductionPass(
diff --git a/source/reduce/remove_instruction_reduction_opportunity.cpp b/source/reduce/remove_instruction_reduction_opportunity.cpp
index 9ca093b..8026204 100644
--- a/source/reduce/remove_instruction_reduction_opportunity.cpp
+++ b/source/reduce/remove_instruction_reduction_opportunity.cpp
@@ -22,6 +22,18 @@
 bool RemoveInstructionReductionOpportunity::PreconditionHolds() { return true; }
 
 void RemoveInstructionReductionOpportunity::Apply() {
+  const uint32_t kNumEntryPointInOperandsBeforeInterfaceIds = 3;
+  for (auto& entry_point : inst_->context()->module()->entry_points()) {
+    opt::Instruction::OperandList new_entry_point_in_operands;
+    for (uint32_t index = 0; index < entry_point.NumInOperands(); index++) {
+      if (index >= kNumEntryPointInOperandsBeforeInterfaceIds &&
+          entry_point.GetSingleWordInOperand(index) == inst_->result_id()) {
+        continue;
+      }
+      new_entry_point_in_operands.push_back(entry_point.GetInOperand(index));
+    }
+    entry_point.SetInOperands(std::move(new_entry_point_in_operands));
+  }
   inst_->context()->KillInst(inst_);
 }
 
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h b/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
deleted file mode 100644
index bc4f137..0000000
--- a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h
+++ /dev/null
@@ -1,48 +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_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
-#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
-
-#include "source/reduce/reduction_opportunity_finder.h"
-
-namespace spvtools {
-namespace reduce {
-
-// A finder for opportunities to remove non-control-flow instructions in blocks
-// in cases where the instruction's id is not referenced.  As well as making the
-// module smaller, removing an instruction that references particular ids may
-// create opportunities for subsequently removing the instructions that
-// generated those ids.
-class RemoveUnreferencedInstructionReductionOpportunityFinder
-    : public ReductionOpportunityFinder {
- public:
-  explicit RemoveUnreferencedInstructionReductionOpportunityFinder(
-      bool remove_constants_and_undefs);
-
-  ~RemoveUnreferencedInstructionReductionOpportunityFinder() override = default;
-
-  std::string GetName() const final;
-
-  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
-
- private:
-  bool remove_constants_and_undefs_;
-};
-
-}  // namespace reduce
-}  // namespace spvtools
-
-#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
similarity index 60%
rename from source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
rename to source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
index ce66691..91ec542 100644
--- a/source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
 
 #include "source/opcode.h"
 #include "source/opt/instruction.h"
@@ -21,14 +21,14 @@
 namespace spvtools {
 namespace reduce {
 
-RemoveUnreferencedInstructionReductionOpportunityFinder::
-    RemoveUnreferencedInstructionReductionOpportunityFinder(
+RemoveUnusedInstructionReductionOpportunityFinder::
+    RemoveUnusedInstructionReductionOpportunityFinder(
         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 {
+RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   for (auto& inst : context->module()->debugs1()) {
@@ -60,13 +60,14 @@
   }
 
   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;
     }
+    if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
+                                                                 inst)) {
+      continue;
+    }
     result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
   }
 
@@ -74,38 +75,9 @@
     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;
+    if (!IsIndependentlyRemovableDecoration(inst)) {
+      continue;
     }
-
-    // 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));
   }
 
@@ -139,9 +111,54 @@
   return result;
 }
 
-std::string RemoveUnreferencedInstructionReductionOpportunityFinder::GetName()
-    const {
-  return "RemoveUnreferencedInstructionReductionOpportunityFinder";
+std::string RemoveUnusedInstructionReductionOpportunityFinder::GetName() const {
+  return "RemoveUnusedInstructionReductionOpportunityFinder";
+}
+
+bool RemoveUnusedInstructionReductionOpportunityFinder::
+    OnlyReferencedByIntimateDecorationOrEntryPointInterface(
+        opt::IRContext* context, const opt::Instruction& inst) const {
+  return context->get_def_use_mgr()->WhileEachUse(
+      &inst, [this](opt::Instruction* user, uint32_t use_index) -> bool {
+        return (user->IsDecoration() &&
+                !IsIndependentlyRemovableDecoration(*user)) ||
+               (user->opcode() == SpvOpEntryPoint && use_index > 2);
+      });
+}
+
+bool RemoveUnusedInstructionReductionOpportunityFinder::
+    IsIndependentlyRemovableDecoration(const opt::Instruction& inst) const {
+  uint32_t decoration;
+  switch (inst.opcode()) {
+    case SpvOpDecorate:
+    case SpvOpDecorateId:
+    case SpvOpDecorateString:
+      decoration = inst.GetSingleWordInOperand(1u);
+      break;
+    case SpvOpMemberDecorate:
+    case SpvOpMemberDecorateString:
+      decoration = inst.GetSingleWordInOperand(2u);
+      break;
+    default:
+      // The instruction is not a decoration.  It is legitimate for this to be
+      // reached: it allows the method to be invoked on arbitrary instructions.
+      return false;
+  }
+
+  // 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:
+      return true;
+    default:
+      return false;
+  }
 }
 
 }  // namespace reduce
diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h
new file mode 100644
index 0000000..cbf6a5b
--- /dev/null
+++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h
@@ -0,0 +1,61 @@
+// 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_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to remove non-control-flow instructions in blocks
+// in cases where the instruction's id is either not referenced at all, or
+// referenced only in a trivial manner (for example, we regard a struct type as
+// unused if it is referenced only by struct layout decorations).  As well as
+// making the module smaller, removing an instruction that references particular
+// ids may create opportunities for subsequently removing the instructions that
+// generated those ids.
+class RemoveUnusedInstructionReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  explicit RemoveUnusedInstructionReductionOpportunityFinder(
+      bool remove_constants_and_undefs);
+
+  ~RemoveUnusedInstructionReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  // Returns true if and only if the only uses of |inst| are by decorations that
+  // relate intimately to the instruction (as opposed to decorations that could
+  // be removed independently), or by interface ids in OpEntryPoint.
+  bool OnlyReferencedByIntimateDecorationOrEntryPointInterface(
+      opt::IRContext* context, const opt::Instruction& inst) const;
+
+  // Returns true if and only if |inst| is a decoration instruction that can
+  // legitimately be removed on its own (rather than one that has to be removed
+  // simultaneously with other instructions).
+  bool IsIndependentlyRemovableDecoration(const opt::Instruction& inst) const;
+
+  bool remove_constants_and_undefs_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_UNREFERENCED_INSTRUCTION_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index b19bba4..fc8aee1 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -24,7 +24,7 @@
         remove_block_test.cpp
         remove_function_test.cpp
         remove_selection_test.cpp
-        remove_unreferenced_instruction_test.cpp
+        remove_unused_instruction_test.cpp
         structured_loop_to_selection_test.cpp
         validation_during_reduction_test.cpp
         conditional_branch_to_simple_conditional_branch_test.cpp
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 59f2803..0de5af1 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -16,7 +16,7 @@
 
 #include "source/opt/build_module.h"
 #include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
-#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
 #include "test/reduce/reduce_test_util.h"
 
 namespace spvtools {
@@ -157,35 +157,17 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %60
+               OpEntryPoint Fragment %4 "main"
                OpExecutionMode %4 OriginUpperLeft
-               OpMemberDecorate %16 0 Offset 0
-               OpDecorate %16 Block
-               OpDecorate %18 DescriptorSet 0
-               OpDecorate %18 Binding 2
-               OpMemberDecorate %25 0 Offset 0
-               OpDecorate %25 Block
-               OpDecorate %27 DescriptorSet 0
-               OpDecorate %27 Binding 1
-               OpDecorate %60 Location 0
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeInt 32 1
           %9 = OpConstant %6 0
-         %16 = OpTypeStruct %6
-         %17 = OpTypePointer Uniform %16
-         %18 = OpVariable %17 Uniform
          %22 = OpTypeBool
         %100 = OpConstantTrue %22
          %24 = OpTypeFloat 32
-         %25 = OpTypeStruct %24
-         %26 = OpTypePointer Uniform %25
-         %27 = OpVariable %26 Uniform
          %31 = OpConstant %24 2
          %56 = OpConstant %6 1
-         %58 = OpTypeVector %24 4
-         %59 = OpTypePointer Output %58
-         %60 = OpVariable %59 Output
          %72 = OpUndef %24
          %74 = OpUndef %6
           %4 = OpFunction %2 None %3
@@ -218,8 +200,7 @@
         return ping_pong_interesting.IsInteresting(binary);
       });
   reducer.AddReductionPass(
-      MakeUnique<RemoveUnreferencedInstructionReductionOpportunityFinder>(
-          false));
+      MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(false));
   reducer.AddReductionPass(
       MakeUnique<OperandToConstReductionOpportunityFinder>());
 
diff --git a/test/reduce/remove_unreferenced_instruction_test.cpp b/test/reduce/remove_unused_instruction_test.cpp
similarity index 62%
rename from test/reduce/remove_unreferenced_instruction_test.cpp
rename to test/reduce/remove_unused_instruction_test.cpp
index 3caf88c..68bc601 100644
--- a/test/reduce/remove_unreferenced_instruction_test.cpp
+++ b/test/reduce/remove_unused_instruction_test.cpp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
 
 #include "source/opt/build_module.h"
 #include "source/reduce/reduction_opportunity.h"
@@ -25,11 +25,11 @@
 
 const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
 
-TEST(RemoveUnreferencedInstructionReductionPassTest, RemoveStores) {
+TEST(RemoveUnusedInstructionReductionPassTest, RemoveStores) {
   // A module with some unused instructions, including some unused OpStore
   // instructions.
 
-  RemoveUnreferencedInstructionReductionOpportunityFinder finder(true);
+  RemoveUnusedInstructionReductionOpportunityFinder finder(true);
 
   const std::string original = R"(
                OpCapability Shader
@@ -223,11 +223,11 @@
   ASSERT_EQ(0, ops.size());
 }
 
-TEST(RemoveUnreferencedInstructionReductionPassTest, Referenced) {
+TEST(RemoveUnusedInstructionReductionPassTest, 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);
+  RemoveUnusedInstructionReductionOpportunityFinder finder(true);
 
   const std::string shader = R"(
                OpCapability Shader
@@ -375,6 +375,189 @@
   ASSERT_EQ(0, ops.size());
 }
 
+TEST(RemoveUnusedResourceVariableTest, RemoveUnusedResourceVariables) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 Block
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 1
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpDecorate %18 DescriptorSet 0
+               OpDecorate %18 Binding 0
+               OpMemberDecorate %19 0 Offset 0
+               OpDecorate %19 BufferBlock
+               OpDecorate %21 DescriptorSet 1
+               OpDecorate %21 Binding 0
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+               OpDecorate %29 DescriptorSet 1
+               OpDecorate %29 Binding 1
+               OpDecorate %32 DescriptorSet 1
+               OpDecorate %32 Binding 2
+               OpDecorate %32 NonReadable
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer Uniform %9
+         %11 = OpVariable %10 Uniform
+         %13 = OpTypePointer Uniform %6
+         %16 = OpTypeStruct %6 %6
+         %17 = OpTypePointer Uniform %16
+         %18 = OpVariable %17 Uniform
+         %19 = OpTypeStruct %6
+         %20 = OpTypePointer Uniform %19
+         %21 = OpVariable %20 Uniform
+         %22 = OpTypeStruct %6
+         %23 = OpTypePointer PushConstant %22
+         %24 = OpVariable %23 PushConstant
+         %25 = OpTypeFloat 32
+         %26 = OpTypeImage %25 2D 0 0 0 1 Unknown
+         %27 = OpTypeSampledImage %26
+         %28 = OpTypePointer UniformConstant %27
+         %29 = OpVariable %28 UniformConstant
+         %30 = OpTypeImage %25 2D 0 0 0 2 Unknown
+         %31 = OpTypePointer UniformConstant %30
+         %32 = OpVariable %31 UniformConstant
+          %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, shader, kReduceAssembleOption);
+
+  auto ops = RemoveUnusedInstructionReductionOpportunityFinder(true)
+                 .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(7, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+  }
+
+  std::string expected_1 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 Block
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpMemberDecorate %19 0 Offset 0
+               OpDecorate %19 BufferBlock
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer Uniform %9
+         %16 = OpTypeStruct %6 %6
+         %17 = OpTypePointer Uniform %16
+         %19 = OpTypeStruct %6
+         %20 = OpTypePointer Uniform %19
+         %22 = OpTypeStruct %6
+         %23 = OpTypePointer PushConstant %22
+         %25 = OpTypeFloat 32
+         %26 = OpTypeImage %25 2D 0 0 0 1 Unknown
+         %27 = OpTypeSampledImage %26
+         %28 = OpTypePointer UniformConstant %27
+         %30 = OpTypeImage %25 2D 0 0 0 2 Unknown
+         %31 = OpTypePointer UniformConstant %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, expected_1, context.get());
+
+  ops = RemoveUnusedInstructionReductionOpportunityFinder(true)
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(6, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+  }
+
+  std::string expected_2 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 Block
+               OpMemberDecorate %16 0 Offset 0
+               OpMemberDecorate %16 1 Offset 4
+               OpDecorate %16 Block
+               OpMemberDecorate %19 0 Offset 0
+               OpDecorate %19 BufferBlock
+               OpMemberDecorate %22 0 Offset 0
+               OpDecorate %22 Block
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpTypeStruct %6
+         %16 = OpTypeStruct %6 %6
+         %19 = OpTypeStruct %6
+         %22 = OpTypeStruct %6
+         %25 = OpTypeFloat 32
+         %26 = OpTypeImage %25 2D 0 0 0 1 Unknown
+         %27 = OpTypeSampledImage %26
+         %30 = OpTypeImage %25 2D 0 0 0 2 Unknown
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, expected_2, context.get());
+
+  ops = RemoveUnusedInstructionReductionOpportunityFinder(true)
+            .GetAvailableOpportunities(context.get());
+  ASSERT_EQ(6, ops.size());
+
+  for (auto& op : ops) {
+    ASSERT_TRUE(op->PreconditionHolds());
+    op->TryToApply();
+  }
+
+  std::string expected_3 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %25 = OpTypeFloat 32
+         %26 = OpTypeImage %25 2D 0 0 0 1 Unknown
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, expected_3, context.get());
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools