reduce: add large tests and fix (#2947)

* Add larger reducer tests. 
* Fix conditional_branch_to_simple_conditional_branch_opportunity pass. 
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
index 2feca4a..0bd93b9 100644
--- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
@@ -73,7 +73,7 @@
         result.push_back(
             MakeUnique<
                 ConditionalBranchToSimpleConditionalBranchReductionOpportunity>(
-                block.terminator(), redirect_to_true));
+                context, block.terminator(), redirect_to_true));
       }
     }
   }
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
index 92c621e..d744773 100644
--- a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
@@ -19,12 +19,15 @@
 namespace spvtools {
 namespace reduce {
 
+using opt::IRContext;
 using opt::Instruction;
 
 ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
     ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
-        Instruction* conditional_branch_instruction, bool redirect_to_true)
-    : conditional_branch_instruction_(conditional_branch_instruction),
+        IRContext* context, Instruction* conditional_branch_instruction,
+        bool redirect_to_true)
+    : context_(context),
+      conditional_branch_instruction_(conditional_branch_instruction),
       redirect_to_true_(redirect_to_true) {}
 
 bool ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
@@ -43,11 +46,24 @@
   uint32_t operand_to_copy =
       redirect_to_true_ ? kTrueBranchOperandIndex : kFalseBranchOperandIndex;
 
+  auto old_successor_block_id =
+      conditional_branch_instruction_->GetSingleWordInOperand(
+          operand_to_modify);
+
   // Do the branch redirection.
   conditional_branch_instruction_->SetInOperand(
       operand_to_modify,
       {conditional_branch_instruction_->GetSingleWordInOperand(
           operand_to_copy)});
+
+  // The old successor block may have phi instructions; these will need to
+  // respect the change in edges.
+  AdaptPhiInstructionsForRemovedEdge(
+      context_->get_instr_block(conditional_branch_instruction_)->id(),
+      context_->cfg()->block(old_successor_block_id));
+
+  // We have changed the CFG.
+  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
 }
 
 }  // namespace reduce
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
index 421906b..1f9cb6d 100644
--- a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
@@ -31,7 +31,8 @@
   // to the true target; otherwise, the true target will be changed to also
   // point to the false target.
   explicit ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
-      opt::Instruction* conditional_branch_instruction, bool redirect_to_true);
+      opt::IRContext* context, opt::Instruction* conditional_branch_instruction,
+      bool redirect_to_true);
 
   bool PreconditionHolds() override;
 
@@ -39,6 +40,7 @@
   void Apply() override;
 
  private:
+  opt::IRContext* context_;
   opt::Instruction* conditional_branch_instruction_;
 
   // If true, the false target will be changed to point to the true target;
diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp
index 2b2b7e6..6f128dc 100644
--- a/source/reduce/reduction_util.cpp
+++ b/source/reduce/reduction_util.cpp
@@ -44,5 +44,22 @@
   return undef_id;
 }
 
+void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
+                                        opt::BasicBlock* to_block) {
+  to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
+    Instruction::OperandList new_in_operands;
+    // Go through the OpPhi's input operands in (variable, parent) pairs.
+    for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
+      // Keep all pairs where the parent is not the block from which the edge
+      // is being removed.
+      if (phi_inst->GetInOperand(index + 1).words[0] != from_id) {
+        new_in_operands.push_back(phi_inst->GetInOperand(index));
+        new_in_operands.push_back(phi_inst->GetInOperand(index + 1));
+      }
+    }
+    phi_inst->SetInOperands(std::move(new_in_operands));
+  });
+}
+
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h
index b8ffb6e..7e7e153 100644
--- a/source/reduce/reduction_util.h
+++ b/source/reduce/reduction_util.h
@@ -30,6 +30,11 @@
 // adding one if it does not exist.
 uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
 
+// Removes any components of |to_block|'s phi instructions relating to
+// |from_id|.
+void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
+                                        opt::BasicBlock* to_block);
+
 }  // namespace reduce
 }  // namespace spvtools
 
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index afc1298..88ea38e 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -168,23 +168,6 @@
 }
 
 void StructuredLoopToSelectionReductionOpportunity::
-    AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, BasicBlock* to_block) {
-  to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
-    Instruction::OperandList new_in_operands;
-    // Go through the OpPhi's input operands in (variable, parent) pairs.
-    for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
-      // Keep all pairs where the parent is not the block from which the edge
-      // is being removed.
-      if (phi_inst->GetInOperand(index + 1).words[0] != from_id) {
-        new_in_operands.push_back(phi_inst->GetInOperand(index));
-        new_in_operands.push_back(phi_inst->GetInOperand(index + 1));
-      }
-    }
-    phi_inst->SetInOperands(std::move(new_in_operands));
-  });
-}
-
-void StructuredLoopToSelectionReductionOpportunity::
     AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
   to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
     // Add to the phi operand an (undef, from_id) pair to reflect the added
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index f6c065b..564811f 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -62,11 +62,6 @@
   void RedirectEdge(uint32_t source_id, uint32_t original_target_id,
                     uint32_t new_target_id);
 
-  // Removes any components of |to_block|'s phi instructions relating to
-  // |from_id|.
-  void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
-                                          opt::BasicBlock* to_block);
-
   // Adds components to |to_block|'s phi instructions to account for a new
   // incoming edge from |from_id|.
   void AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 189e922..a650d3b 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -237,6 +237,202 @@
   CheckEqual(kEnv, expected, binary_out);
 }
 
+bool InterestingWhileOpcodeExists(const std::vector<uint32_t>& binary,
+                                  uint32_t opcode, uint32_t count, bool dump) {
+  if (dump) {
+    std::stringstream ss;
+    ss << "temp_" << count << ".spv";
+    DumpShader(binary, ss.str().c_str());
+  }
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size());
+  assert(context);
+  bool interesting = false;
+  for (auto& function : *context->module()) {
+    context->cfg()->ForEachBlockInPostOrder(
+        &*function.begin(), [opcode, &interesting](BasicBlock* block) -> void {
+          for (auto& inst : *block) {
+            if (inst.opcode() == opcode) {
+              interesting = true;
+              break;
+            }
+          }
+        });
+    if (interesting) {
+      break;
+    }
+  }
+  return interesting;
+}
+
+bool InterestingWhileIMulReachable(const std::vector<uint32_t>& binary,
+                                   uint32_t count) {
+  return InterestingWhileOpcodeExists(binary, SpvOpIMul, count, false);
+}
+
+bool InterestingWhileSDivReachable(const std::vector<uint32_t>& binary,
+                                   uint32_t count) {
+  return InterestingWhileOpcodeExists(binary, SpvOpSDiv, count, false);
+}
+
+// The shader below was derived from the following GLSL, and optimized.
+// #version 310 es
+// precision highp float;
+// layout(location = 0) out vec4 _GLF_color;
+// int foo() {
+//    int x = 1;
+//    int y;
+//    x = y / x;   // SDiv
+//    return x;
+// }
+// void main() {
+//    int c;
+//    while (bool(c)) {
+//        do {
+//            if (bool(c)) {
+//                if (bool(c)) {
+//                    ++c;
+//                } else {
+//                    _GLF_color.x = float(c*c);  // IMul
+//                }
+//                return;
+//            }
+//        } while(bool(foo()));
+//        return;
+//    }
+// }
+const std::string kShaderWithLoopsDivAndMul = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %49
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %49 "_GLF_color"
+               OpDecorate %49 Location 0
+               OpDecorate %52 RelaxedPrecision
+               OpDecorate %77 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %12 = OpConstant %6 1
+         %27 = OpTypeBool
+         %28 = OpTypeInt 32 0
+         %29 = OpConstant %28 0
+         %46 = OpTypeFloat 32
+         %47 = OpTypeVector %46 4
+         %48 = OpTypePointer Output %47
+         %49 = OpVariable %48 Output
+         %54 = OpTypePointer Output %46
+         %64 = OpConstantFalse %27
+         %67 = OpConstantTrue %27
+         %81 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %61
+         %61 = OpLabel
+               OpLoopMerge %60 %63 None
+               OpBranch %20
+         %20 = OpLabel
+         %30 = OpINotEqual %27 %81 %29
+               OpLoopMerge %22 %23 None
+               OpBranchConditional %30 %21 %22
+         %21 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %33 %38 None
+               OpBranch %32
+         %32 = OpLabel
+               OpSelectionMerge %38 None
+               OpBranchConditional %30 %37 %38
+         %37 = OpLabel
+               OpSelectionMerge %42 None
+               OpBranchConditional %30 %41 %45
+         %41 = OpLabel
+               OpBranch %42
+         %45 = OpLabel
+         %52 = OpIMul %6 %81 %81
+         %53 = OpConvertSToF %46 %52
+         %55 = OpAccessChain %54 %49 %29
+               OpStore %55 %53
+               OpBranch %42
+         %42 = OpLabel
+               OpBranch %33
+         %38 = OpLabel
+         %77 = OpSDiv %6 %81 %12
+         %58 = OpINotEqual %27 %77 %29
+               OpBranchConditional %58 %31 %33
+         %33 = OpLabel
+         %86 = OpPhi %27 %67 %42 %64 %38
+               OpSelectionMerge %68 None
+               OpBranchConditional %86 %22 %68
+         %68 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+               OpBranch %20
+         %22 = OpLabel
+         %90 = OpPhi %27 %64 %20 %86 %33 %67 %68
+               OpSelectionMerge %70 None
+               OpBranchConditional %90 %60 %70
+         %70 = OpLabel
+               OpBranch %60
+         %63 = OpLabel
+               OpBranch %61
+         %60 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+TEST(ReducerTest, ShaderReduceWhileMulReachable) {
+  Reducer reducer(kEnv);
+
+  reducer.SetInterestingnessFunction(InterestingWhileIMulReachable);
+  reducer.AddDefaultReductionPasses();
+  reducer.SetMessageConsumer(kMessageConsumer);
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(kEnv);
+
+  ASSERT_TRUE(
+      t.Assemble(kShaderWithLoopsDivAndMul, &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);
+}
+
+TEST(ReducerTest, ShaderReduceWhileDivReachable) {
+  Reducer reducer(kEnv);
+
+  reducer.SetInterestingnessFunction(InterestingWhileSDivReachable);
+  reducer.AddDefaultReductionPasses();
+  reducer.SetMessageConsumer(kMessageConsumer);
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(kEnv);
+
+  ASSERT_TRUE(
+      t.Assemble(kShaderWithLoopsDivAndMul, &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);
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools