New spirv-reduce reduction pass: operand to dominating id. (#2099)

* Added a reduction pass to replace ids with ids of the same type that dominate them.
* Introduce helper method for querying whether an operand type is an input id.
diff --git a/source/operand.cpp b/source/operand.cpp
index c97b13f..923074e 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -411,6 +411,21 @@
   }
 }
 
+bool spvIsInIdType(spv_operand_type_t type) {
+  if (!spvIsIdType(type)) {
+    // If it is not an ID it cannot be an input ID.
+    return false;
+  }
+  switch (type) {
+    // Blacklist non-input IDs.
+    case SPV_OPERAND_TYPE_TYPE_ID:
+    case SPV_OPERAND_TYPE_RESULT_ID:
+      return false;
+    default:
+      return true;
+  }
+}
+
 std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
     SpvOp opcode) {
   std::function<bool(unsigned index)> out;
diff --git a/source/operand.h b/source/operand.h
index 76f16f7..15a1825 100644
--- a/source/operand.h
+++ b/source/operand.h
@@ -131,6 +131,9 @@
 // Is the operand an ID?
 bool spvIsIdType(spv_operand_type_t type);
 
+// Is the operand an input ID?
+bool spvIsInIdType(spv_operand_type_t type);
+
 // Takes the opcode of an instruction and returns
 // a function object that will return true if the index
 // of the operand can be forward declared. This function will
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 6a9d954..034da76 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -618,15 +618,8 @@
 inline bool Instruction::WhileEachInId(
     const std::function<bool(uint32_t*)>& f) {
   for (auto& opnd : operands_) {
-    switch (opnd.type) {
-      case SPV_OPERAND_TYPE_RESULT_ID:
-      case SPV_OPERAND_TYPE_TYPE_ID:
-        break;
-      default:
-        if (spvIsIdType(opnd.type)) {
-          if (!f(&opnd.words[0])) return false;
-        }
-        break;
+    if (spvIsInIdType(opnd.type)) {
+      if (!f(&opnd.words[0])) return false;
     }
   }
   return true;
@@ -635,15 +628,8 @@
 inline bool Instruction::WhileEachInId(
     const std::function<bool(const uint32_t*)>& f) const {
   for (const auto& opnd : operands_) {
-    switch (opnd.type) {
-      case SPV_OPERAND_TYPE_RESULT_ID:
-      case SPV_OPERAND_TYPE_TYPE_ID:
-        break;
-      default:
-        if (spvIsIdType(opnd.type)) {
-          if (!f(&opnd.words[0])) return false;
-        }
-        break;
+    if (spvIsInIdType(opnd.type)) {
+      if (!f(&opnd.words[0])) return false;
     }
   }
   return true;
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 995e1e6..fc7369f 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -14,6 +14,7 @@
 set(SPIRV_TOOLS_REDUCE_SOURCES
         change_operand_reduction_opportunity.h
         operand_to_const_reduction_pass.h
+        operand_to_dominating_id_reduction_pass.h
         reducer.h
         reduction_opportunity.h
         reduction_pass.h
@@ -22,6 +23,7 @@
 
         change_operand_reduction_opportunity.cpp
         operand_to_const_reduction_pass.cpp
+        operand_to_dominating_id_reduction_pass.cpp
         reducer.cpp
         reduction_opportunity.cpp
         reduction_pass.cpp
diff --git a/source/reduce/change_operand_reduction_opportunity.h b/source/reduce/change_operand_reduction_opportunity.h
index ba67cd0..9e43ec9 100644
--- a/source/reduce/change_operand_reduction_opportunity.h
+++ b/source/reduce/change_operand_reduction_opportunity.h
@@ -34,7 +34,7 @@
                                     uint32_t new_id)
       : inst_(inst),
         operand_index_(operand_index),
-        original_id_(inst_->GetOperand(operand_index_).words[0]),
+        original_id_(inst->GetOperand(operand_index).words[0]),
         original_type_(inst->GetOperand(operand_index).type),
         new_id_(new_id) {}
 
diff --git a/source/reduce/operand_to_const_reduction_pass.cpp b/source/reduce/operand_to_const_reduction_pass.cpp
index b1b1385..35c0f4b 100644
--- a/source/reduce/operand_to_const_reduction_pass.cpp
+++ b/source/reduce/operand_to_const_reduction_pass.cpp
@@ -44,11 +44,7 @@
           // of a ChangeOperandReductionOpportunity
           for (uint32_t index = 0; index < inst.NumOperands(); index++) {
             const auto& operand = inst.GetOperand(index);
-            if (spvIsIdType(operand.type)) {
-              if (operand.type == SPV_OPERAND_TYPE_RESULT_ID ||
-                  operand.type == SPV_OPERAND_TYPE_TYPE_ID) {
-                continue;
-              }
+            if (spvIsInIdType(operand.type)) {
               const auto id = operand.words[0];
               auto def = context->get_def_use_mgr()->GetDef(id);
               if (spvOpcodeIsConstant(def->opcode())) {
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.cpp b/source/reduce/operand_to_dominating_id_reduction_pass.cpp
new file mode 100644
index 0000000..9280a41
--- /dev/null
+++ b/source/reduce/operand_to_dominating_id_reduction_pass.cpp
@@ -0,0 +1,114 @@
+// 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 "operand_to_dominating_id_reduction_pass.h"
+#include "change_operand_reduction_opportunity.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+OperandToDominatingIdReductionPass::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Go through every instruction in every block, considering it as a potential
+  // dominator of other instructions.  We choose this order for two reasons:
+  //
+  // (1) it is profitable for multiple opportunities to replace the same id x by
+  // different dominating ids y and z to be discontiguous, as they are
+  // incompatible.
+  //
+  // (2) We want to prioritise opportunities to replace an id with a more
+  // distant dominator.  Intuitively, in a human-readable programming language
+  // if we have a complex expression e with many sub-expressions, we would like
+  // to prioritise replacing e with its smallest sub-expressions; generalising
+  // this idea to dominating ids this roughly corresponds to more distant
+  // dominators.
+  for (auto& function : *context->module()) {
+    for (auto dominating_block = function.begin();
+         dominating_block != function.end(); ++dominating_block) {
+      for (auto& dominating_inst : *dominating_block) {
+        if (dominating_inst.HasResultId() && dominating_inst.type_id()) {
+          // Consider replacing any operand with matching type in a dominated
+          // instruction with the id generated by this instruction.
+          GetOpportunitiesForDominatingInst(
+              &result, &dominating_inst, dominating_block, &function, context);
+        }
+      }
+    }
+  }
+  return result;
+}
+
+void OperandToDominatingIdReductionPass::GetOpportunitiesForDominatingInst(
+    std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+    opt::Instruction* candidate_dominator,
+    opt::Function::iterator candidate_dominator_block, opt::Function* function,
+    opt::IRContext* context) const {
+  assert(candidate_dominator->HasResultId());
+  assert(candidate_dominator->type_id());
+  auto dominator_analysis = context->GetDominatorAnalysis(function);
+  // SPIR-V requires a block to precede all blocks it dominates, so it suffices
+  // to search from the candidate dominator block onwards.
+  for (auto block = candidate_dominator_block; block != function->end();
+       ++block) {
+    if (!dominator_analysis->Dominates(&*candidate_dominator_block, &*block)) {
+      // If the candidate dominator block doesn't dominate this block then there
+      // cannot be any of the desired reduction opportunities in this block.
+      continue;
+    }
+    for (auto& inst : *block) {
+      // We iterate through the operands using an explicit index (rather
+      // than using a lambda) so that we use said index in the construction
+      // of a ChangeOperandReductionOpportunity
+      for (uint32_t index = 0; index < inst.NumOperands(); index++) {
+        const auto& operand = inst.GetOperand(index);
+        if (spvIsInIdType(operand.type)) {
+          const auto id = operand.words[0];
+          auto def = context->get_def_use_mgr()->GetDef(id);
+          assert(def);
+          if (!context->get_instr_block(def)) {
+            // The definition does not come from a block; e.g. it might be a
+            // constant.  It is thus not relevant to this pass.
+            continue;
+          }
+          // Sanity check that we don't get here if the argument is a constant.
+          assert(!context->get_constant_mgr()->GetConstantFromInst(def));
+          if (def->type_id() != candidate_dominator->type_id()) {
+            // The types need to match.
+            continue;
+          }
+          if (candidate_dominator != def &&
+              dominator_analysis->Dominates(candidate_dominator, def)) {
+            // A hit: the candidate dominator strictly dominates the definition.
+            opportunities->push_back(
+                MakeUnique<ChangeOperandReductionOpportunity>(
+                    &inst, index, candidate_dominator->result_id()));
+          }
+        }
+      }
+    }
+  }
+}
+
+std::string OperandToDominatingIdReductionPass::GetName() const {
+  return "OperandToDominatingIdReductionPass";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/operand_to_dominating_id_reduction_pass.h b/source/reduce/operand_to_dominating_id_reduction_pass.h
new file mode 100644
index 0000000..b62b6ae
--- /dev/null
+++ b/source/reduce/operand_to_dominating_id_reduction_pass.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
+#define SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
+
+#include "reduction_pass.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A reduction pass that aims to bring to SPIR-V (and generalize) the idea from
+// human-readable languages of e.g. replacing an expression with one of its
+// arguments, (x + y) -> x, or with a reference to an identifier that was
+// assigned to higher up in the program.  The generalization of this is to
+// replace an id with a different id of the same type defined in some
+// dominating instruction.
+//
+// If id x is defined and then used several times, changing each use of x to
+// some dominating definition may eventually allow the statement defining x
+// to be eliminated by another pass.
+class OperandToDominatingIdReductionPass : public ReductionPass {
+ public:
+  // Creates the reduction pass in the context of the given target environment
+  // |target_env|
+  explicit OperandToDominatingIdReductionPass(const spv_target_env target_env)
+      : ReductionPass(target_env) {}
+
+  ~OperandToDominatingIdReductionPass() override = default;
+
+  // The name of this pass.
+  std::string GetName() const final;
+
+ protected:
+  // Finds all opportunities for replacing an operand with a dominating
+  // instruction in a given module.
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  void GetOpportunitiesForDominatingInst(
+      std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
+      opt::Instruction* dominating_instruction,
+      opt::Function::iterator candidate_dominator_block,
+      opt::Function* function, opt::IRContext* context) const;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_OPERAND_TO_DOMINATING_ID_REDUCTION_PASS_H_
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index e3c1e95..828bf68 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -14,6 +14,7 @@
 
 add_spvtools_unittest(TARGET reduce
         SRCS operand_to_constant_reduction_pass_test.cpp
+        operand_to_dominating_id_reduction_pass_test.cpp
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
diff --git a/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp b/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp
new file mode 100644
index 0000000..cc0de65
--- /dev/null
+++ b/test/reduce/operand_to_dominating_id_reduction_pass_test.cpp
@@ -0,0 +1,196 @@
+// 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/operand_to_dominating_id_reduction_pass.h"
+#include "reduce_test_util.h"
+#include "source/opt/build_module.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(OperandToDominatingIdReductionPassTest, BasicCheck) {
+  std::string original = 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
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpIAdd %6 %11 %12
+               OpStore %10 %13
+         %15 = OpLoad %6 %10
+               OpStore %14 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, original, kReduceAssembleOption);
+  const auto pass = TestSubclass<OperandToDominatingIdReductionPass>(env);
+  const auto ops = pass.WrapGetAvailableOpportunities(context.get());
+  ASSERT_EQ(10, ops.size());
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after_op_0 = 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
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpIAdd %6 %11 %12
+               OpStore %8 %13 ; %10 -> %8
+         %15 = OpLoad %6 %10
+               OpStore %14 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+
+  std::string after_op_1 = 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
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpIAdd %6 %11 %12
+               OpStore %8 %13 ; %10 -> %8
+         %15 = OpLoad %6 %8 ; %10 -> %8
+               OpStore %14 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_1, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+
+  std::string after_op_2 = 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
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpIAdd %6 %11 %12
+               OpStore %8 %13 ; %10 -> %8
+         %15 = OpLoad %6 %8 ; %10 -> %8
+               OpStore %8 %15 ; %14 -> %8
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_2, context.get());
+
+  // The precondition has been disabled by an earlier opportunity's application.
+  ASSERT_FALSE(ops[3]->PreconditionHolds());
+
+  ASSERT_TRUE(ops[4]->PreconditionHolds());
+  ops[4]->TryToApply();
+
+  std::string after_op_4 = 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
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpIAdd %6 %11 %11 ; %12 -> %11
+               OpStore %8 %13 ; %10 -> %8
+         %15 = OpLoad %6 %8 ; %10 -> %8
+               OpStore %8 %15 ; %14 -> %8
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, after_op_4, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index ac82d66..6b0af09 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -21,6 +21,7 @@
 #include "source/opt/ir_context.h"
 #include "source/opt/log.h"
 #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_unreferenced_instruction_reduction_pass.h"
 #include "source/spirv_reducer_options.h"
@@ -205,6 +206,8 @@
   reducer.AddReductionPass(
       spvtools::MakeUnique<OperandToConstReductionPass>(target_env));
   reducer.AddReductionPass(
+      spvtools::MakeUnique<OperandToDominatingIdReductionPass>(target_env));
+  reducer.AddReductionPass(
       spvtools::MakeUnique<RemoveUnreferencedInstructionReductionPass>(
           target_env));