Add RemoveOpNameInstruction reduction pass (#2187)

Add a spirv-reduce pass which removes OpName and OpMemberName instructions.

This is useful to enable other reduction passes, e.g. RemoveUnreferencedInstruction may not be able to remove an instruction creating an id whose only usage is an OpName for this id.
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 1a1cd7d..57475ac 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -19,6 +19,7 @@
         reduction_opportunity.h
         reduction_pass.h
         remove_instruction_reduction_opportunity.h
+        remove_opname_instruction_reduction_pass.h
         remove_unreferenced_instruction_reduction_pass.h
         structured_loop_to_selection_reduction_opportunity.h
         structured_loop_to_selection_reduction_pass.h
@@ -31,6 +32,7 @@
         reduction_pass.cpp
         remove_instruction_reduction_opportunity.cpp
         remove_unreferenced_instruction_reduction_pass.cpp
+        remove_opname_instruction_reduction_pass.cpp
         structured_loop_to_selection_reduction_opportunity.cpp
         structured_loop_to_selection_reduction_pass.cpp
         )
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.cpp b/source/reduce/remove_opname_instruction_reduction_pass.cpp
new file mode 100644
index 0000000..bf99bc5
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_pass.cpp
@@ -0,0 +1,44 @@
+// 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 "remove_opname_instruction_reduction_pass.h"
+#include "remove_instruction_reduction_opportunity.h"
+#include "source/opcode.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveOpNameInstructionReductionPass::GetAvailableOpportunities(
+    opt::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 RemoveOpNameInstructionReductionPass::GetName() const {
+  return "RemoveOpNameInstructionReductionPass";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_opname_instruction_reduction_pass.h b/source/reduce/remove_opname_instruction_reduction_pass.h
new file mode 100644
index 0000000..d20b6e1
--- /dev/null
+++ b/source/reduce/remove_opname_instruction_reduction_pass.h
@@ -0,0 +1,50 @@
+// 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_PASS_H_
+#define SOURCE_REDUCE_REMOVE_OPNAME_INSTRUCTION_REDUCTION_PASS_H_
+
+#include "reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A reduction pass for removing OpName instructions.  As well as making the
+// module smaller, removing an OpName instruction may create opportunities to
+// remove the instruction that create the id to which the OpName applies.
+class RemoveOpNameInstructionReductionPass : public ReductionPass {
+ public:
+  // Creates the reduction pass in the context of the given target environment
+  // |target_env|
+  explicit RemoveOpNameInstructionReductionPass(const spv_target_env target_env)
+      : ReductionPass(target_env) {}
+
+  ~RemoveOpNameInstructionReductionPass() override = default;
+
+  // The name of this pass.
+  std::string GetName() const final;
+
+ protected:
+  // Finds all opportunities for removing opName instructions in the
+  // given module.
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_OpName_INSTRUCTION_REDUCTION_PASS_H_
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index 235d74a..69731a6 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -18,6 +18,7 @@
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
+        remove_opname_instruction_reduction_pass_test.cpp
         remove_unreferenced_instruction_reduction_pass_test.cpp
         structured_loop_to_selection_reduction_pass_test.cpp
         LIBS SPIRV-Tools-reduce
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 5441a56..4ce68cc 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -16,6 +16,7 @@
 
 #include "source/reduce/operand_to_const_reduction_pass.h"
 #include "source/reduce/reducer.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
 
 namespace spvtools {
@@ -27,7 +28,7 @@
                    const spv_position_t& /*position*/,
                    const char* /*message*/) {}
 
-// This changes is its mind each time IsInteresting is invoked as to whether the
+// 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
 // interleave in interesting ways for a while, and then always succeed after
@@ -238,6 +239,77 @@
   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 %7
+               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 much
+  // 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<RemoveOpNameInstructionReductionPass>(env));
+  reducer.AddReductionPass(
+      MakeUnique<RemoveUnreferencedInstructionReductionPass>(env));
+
+  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.Run(std::move(binary_in), &binary_out, reducer_options);
+
+  CheckEqual(env, expected, binary_out);
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
\ No newline at end of file
diff --git a/test/reduce/remove_opname_instruction_reduction_pass_test.cpp b/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
new file mode 100644
index 0000000..38a2d7f
--- /dev/null
+++ b/test/reduce/remove_opname_instruction_reduction_pass_test.cpp
@@ -0,0 +1,216 @@
+// 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 "reduce_test_util.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.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 pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(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 pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(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;
+  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+
+  {
+    // Check the right number of opportunities is detected
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, original, kReduceAssembleOption);
+    const auto ops = pass.WrapGetAvailableOpportunities(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 = pass.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;
+  auto pass = TestSubclass<RemoveOpNameInstructionReductionPass>(env);
+
+  {
+    // Check the right number of opportunities is detected
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, original, kReduceAssembleOption);
+    const auto ops = pass.WrapGetAvailableOpportunities(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 = pass.TryApplyReduction(binary);
+    CheckEqual(env, expected, reduced_binary);
+  }
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 9b9748d..390724a 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -23,6 +23,7 @@
 #include "source/reduce/operand_to_const_reduction_pass.h"
 #include "source/reduce/operand_to_dominating_id_reduction_pass.h"
 #include "source/reduce/reducer.h"
+#include "source/reduce/remove_opname_instruction_reduction_pass.h"
 #include "source/reduce/remove_unreferenced_instruction_reduction_pass.h"
 #include "source/reduce/structured_loop_to_selection_reduction_pass.h"
 #include "source/spirv_reducer_options.h"
@@ -205,6 +206,8 @@
       });
 
   reducer.AddReductionPass(
+      spvtools::MakeUnique<RemoveOpNameInstructionReductionPass>(target_env));
+  reducer.AddReductionPass(
       spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
   reducer.AddReductionPass(
       spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));