Add IsReachable function to IRContext (#4323)

There was a lot of code in the codebase that would get the dominator
analysis for a function and then use it to check whether a block is
reachable. In the fuzzer, a utility method had been introduced to make
this more concise, but it was not being used consistently.

This change moves the utility method to IRContext, so that it can be
used throughout the codebase, and refactors all existing checks for
block reachability to use the utility method.
diff --git a/source/fuzz/available_instructions.cpp b/source/fuzz/available_instructions.cpp
index e25ed90..0db8b20 100644
--- a/source/fuzz/available_instructions.cpp
+++ b/source/fuzz/available_instructions.cpp
@@ -44,7 +44,7 @@
     // Consider every reachable block in the function.
     auto dominator_analysis = ir_context->GetDominatorAnalysis(&function);
     for (auto& block : function) {
-      if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, &block)) {
+      if (!ir_context->IsReachable(block)) {
         // The block is not reachable.
         continue;
       }
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index f67efc6..9e95190 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -111,10 +111,8 @@
   // module.
   std::vector<opt::BasicBlock*> reachable_blocks;
 
-  const auto* dominator_analysis =
-      GetIRContext()->GetDominatorAnalysis(function);
   for (auto& block : *function) {
-    if (dominator_analysis->IsReachable(&block)) {
+    if (GetIRContext()->IsReachable(block)) {
       reachable_blocks.push_back(&block);
     }
   }
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp b/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
index af27a5d..89e1437 100644
--- a/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
@@ -31,8 +31,7 @@
   for (const auto& function : *GetIRContext()->module()) {
     std::vector<const opt::BasicBlock*> reachable_blocks;
     for (const auto& block : function) {
-      if (GetIRContext()->GetDominatorAnalysis(&function)->IsReachable(
-              &block)) {
+      if (GetIRContext()->IsReachable(block)) {
         reachable_blocks.push_back(&block);
       }
     }
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
index 54e589c..7b436c1 100644
--- a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
@@ -47,7 +47,7 @@
 
         // The block containing the instruction we are going to insert before
         // must be reachable.
-        if (!fuzzerutil::BlockIsReachableInItsFunction(GetIRContext(), block)) {
+        if (!GetIRContext()->IsReachable(*block)) {
           return;
         }
 
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 08b927e..4beb67f 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -252,11 +252,11 @@
     return false;
   }
 
-  // |block_id| must be reachable and be dominated by |loop_header|.
+  // |block| must be reachable and be dominated by |loop_header|.
   opt::DominatorAnalysis* dominator_analysis =
       context->GetDominatorAnalysis(loop_header->GetParent());
-  return dominator_analysis->IsReachable(block_id) &&
-         dominator_analysis->Dominates(loop_header_id, block_id);
+  return context->IsReachable(*block) &&
+         dominator_analysis->Dominates(loop_header, block);
 }
 
 bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
@@ -284,13 +284,6 @@
   return block->end();
 }
 
-bool BlockIsReachableInItsFunction(opt::IRContext* context,
-                                   opt::BasicBlock* bb) {
-  auto enclosing_function = bb->GetParent();
-  return context->GetDominatorAnalysis(enclosing_function)
-      ->Dominates(enclosing_function->entry().get(), bb);
-}
-
 bool CanInsertOpcodeBeforeInstruction(
     SpvOp opcode, const opt::BasicBlock::iterator& instruction_in_block) {
   if (instruction_in_block->PreviousNode() &&
@@ -660,13 +653,12 @@
     // It is not OK for a definition to use itself.
     return false;
   }
-  auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
-  if (!dominator_analysis->IsReachable(
-          context->get_instr_block(use_instruction)) ||
-      !dominator_analysis->IsReachable(context->get_instr_block(id))) {
+  if (!context->IsReachable(*context->get_instr_block(use_instruction)) ||
+      !context->IsReachable(*context->get_instr_block(id))) {
     // Skip unreachable blocks.
     return false;
   }
+  auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
   if (use_instruction->opcode() == SpvOpPhi) {
     // In the case where the use is an operand to OpPhi, it is actually the
     // *parent* block associated with the operand that must be dominated by
@@ -704,8 +696,8 @@
   }
   const auto* dominator_analysis =
       context->GetDominatorAnalysis(function_enclosing_instruction);
-  if (dominator_analysis->IsReachable(context->get_instr_block(instruction)) &&
-      dominator_analysis->IsReachable(context->get_instr_block(id)) &&
+  if (context->IsReachable(*context->get_instr_block(instruction)) &&
+      context->IsReachable(*context->get_instr_block(id)) &&
       dominator_analysis->Dominates(id_definition, instruction)) {
     // The id's definition dominates the instruction, and both the definition
     // and the instruction are in reachable blocks, thus the id is available at
@@ -715,8 +707,7 @@
   if (id_definition->opcode() == SpvOpVariable &&
       function_enclosing_instruction ==
           context->get_instr_block(id)->GetParent()) {
-    assert(!dominator_analysis->IsReachable(
-               context->get_instr_block(instruction)) &&
+    assert(!context->IsReachable(*context->get_instr_block(instruction)) &&
            "If the instruction were in a reachable block we should already "
            "have returned true.");
     // The id is a variable and it is in the same function as |instruction|.
@@ -1883,7 +1874,7 @@
   // all its dependencies satisfy domination rules (i.e. all id operands
   // dominate that instruction).
   for (const auto& block : *mutated_block->GetParent()) {
-    if (!dominator_analysis.IsReachable(&block)) {
+    if (!ir_context->IsReachable(block)) {
       // If some block is not reachable then we don't need to worry about the
       // preservation of domination rules for its instructions.
       continue;
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index dd7bd96..b956507 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -108,11 +108,6 @@
 opt::BasicBlock::iterator GetIteratorForInstruction(
     opt::BasicBlock* block, const opt::Instruction* inst);
 
-// Returns true if and only if there is a path to |bb| from the entry block of
-// the function that contains |bb|.
-bool BlockIsReachableInItsFunction(opt::IRContext* context,
-                                   opt::BasicBlock* bb);
-
 // Determines whether it is OK to insert an instruction with opcode |opcode|
 // before |instruction_in_block|.
 bool CanInsertOpcodeBeforeInstruction(
diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp
index 82e8cd8..df700ce 100644
--- a/source/fuzz/transformation_add_dead_block.cpp
+++ b/source/fuzz/transformation_add_dead_block.cpp
@@ -79,9 +79,7 @@
   }
 
   // |existing_block| must be reachable.
-  opt::DominatorAnalysis* dominator_analysis =
-      ir_context->GetDominatorAnalysis(existing_block->GetParent());
-  if (!dominator_analysis->IsReachable(existing_block->id())) {
+  if (!ir_context->IsReachable(*existing_block)) {
     return false;
   }
 
@@ -94,6 +92,8 @@
   // the selection construct, its header |existing_block| will not dominate the
   // merge block |successor_block_id|, which is invalid. Thus, |existing_block|
   // must dominate |successor_block_id|.
+  opt::DominatorAnalysis* dominator_analysis =
+      ir_context->GetDominatorAnalysis(existing_block->GetParent());
   if (!dominator_analysis->Dominates(existing_block->id(),
                                      successor_block_id)) {
     return false;
diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp
index ad46ce7..32080ca 100644
--- a/source/fuzz/transformation_add_dead_break.cpp
+++ b/source/fuzz/transformation_add_dead_break.cpp
@@ -134,7 +134,7 @@
     return false;
   }
 
-  if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, bb_to)) {
+  if (!ir_context->IsReachable(*bb_to)) {
     // If the target of the break is unreachable, we conservatively do not
     // allow adding a dead break, to avoid the compilations that arise due to
     // the lack of sensible dominance information for unreachable blocks.
diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp
index be6294e..f2b9ab3 100644
--- a/source/fuzz/transformation_add_dead_continue.cpp
+++ b/source/fuzz/transformation_add_dead_continue.cpp
@@ -83,8 +83,7 @@
   auto continue_block =
       ir_context->cfg()->block(loop_header)->ContinueBlockId();
 
-  if (!fuzzerutil::BlockIsReachableInItsFunction(
-          ir_context, ir_context->cfg()->block(continue_block))) {
+  if (!ir_context->IsReachable(*ir_context->cfg()->block(continue_block))) {
     // If the loop's continue block is unreachable, we conservatively do not
     // allow adding a dead continue, to avoid the compilations that arise due to
     // the lack of sensible dominance information for unreachable blocks.
diff --git a/source/fuzz/transformation_flatten_conditional_branch.cpp b/source/fuzz/transformation_flatten_conditional_branch.cpp
index b8c6de0..127e762 100644
--- a/source/fuzz/transformation_flatten_conditional_branch.cpp
+++ b/source/fuzz/transformation_flatten_conditional_branch.cpp
@@ -441,17 +441,17 @@
          header->terminator()->opcode() == SpvOpBranchConditional &&
          "|header| must be the header of a conditional.");
 
+  // |header| must be reachable.
+  if (!ir_context->IsReachable(*header)) {
+    return false;
+  }
+
   auto enclosing_function = header->GetParent();
   auto dominator_analysis =
       ir_context->GetDominatorAnalysis(enclosing_function);
   auto postdominator_analysis =
       ir_context->GetPostDominatorAnalysis(enclosing_function);
 
-  // |header| must be reachable.
-  if (!dominator_analysis->IsReachable(header)) {
-    return false;
-  }
-
   // Check that the header and the merge block describe a single-entry,
   // single-exit region.
   if (!dominator_analysis->Dominates(header->id(), merge_block_id) ||
diff --git a/source/fuzz/transformation_outline_function.cpp b/source/fuzz/transformation_outline_function.cpp
index 84e8ac2..3140fa6 100644
--- a/source/fuzz/transformation_outline_function.cpp
+++ b/source/fuzz/transformation_outline_function.cpp
@@ -180,8 +180,7 @@
       // predecessors. If it does, then we do not regard the region as single-
       // entry-single-exit and hence do not outline it.
       for (auto pred : ir_context->cfg()->preds(block.id())) {
-        if (!fuzzerutil::BlockIsReachableInItsFunction(
-                ir_context, ir_context->cfg()->block(pred))) {
+        if (!ir_context->IsReachable(*ir_context->cfg()->block(pred))) {
           // The predecessor is unreachable.
           return false;
         }
diff --git a/source/fuzz/transformation_propagate_instruction_down.cpp b/source/fuzz/transformation_propagate_instruction_down.cpp
index 7713562..c3b7c4d 100644
--- a/source/fuzz/transformation_propagate_instruction_down.cpp
+++ b/source/fuzz/transformation_propagate_instruction_down.cpp
@@ -386,11 +386,8 @@
     return false;
   }
 
-  const auto* dominator_analysis =
-      ir_context->GetDominatorAnalysis(block->GetParent());
-
   // |block| must be reachable.
-  if (!dominator_analysis->IsReachable(block)) {
+  if (!ir_context->IsReachable(*block)) {
     return false;
   }
 
@@ -430,6 +427,9 @@
   auto phi_block_id =
       GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids);
 
+  const auto* dominator_analysis =
+      ir_context->GetDominatorAnalysis(block->GetParent());
+
   // Make sure we can adjust all users of the propagated instruction.
   return ir_context->get_def_use_mgr()->WhileEachUse(
       inst_to_propagate,
@@ -537,7 +537,7 @@
 
   // Check that |merge_block_id| is reachable in the CFG and |block_id|
   // dominates |merge_block_id|.
-  if (!dominator_analysis->IsReachable(merge_block_id) ||
+  if (!ir_context->IsReachable(*ir_context->cfg()->block(merge_block_id)) ||
       !dominator_analysis->Dominates(block_id, merge_block_id)) {
     return 0;
   }
diff --git a/source/fuzz/transformation_push_id_through_variable.cpp b/source/fuzz/transformation_push_id_through_variable.cpp
index 0df1da6..ff52516 100644
--- a/source/fuzz/transformation_push_id_through_variable.cpp
+++ b/source/fuzz/transformation_push_id_through_variable.cpp
@@ -61,7 +61,7 @@
 
   // The instruction to insert before must belong to a reachable block.
   auto basic_block = ir_context->get_instr_block(instruction_to_insert_before);
-  if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, basic_block)) {
+  if (!ir_context->IsReachable(*basic_block)) {
     return false;
   }
 
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
index 14b5d36..39b5074 100644
--- a/source/opt/block_merge_util.cpp
+++ b/source/opt/block_merge_util.cpp
@@ -104,9 +104,7 @@
   }
 
   // Don't bother trying to merge unreachable blocks.
-  if (auto dominators = context->GetDominatorAnalysis(block->GetParent())) {
-    if (!dominators->IsReachable(block)) return false;
-  }
+  if (!context->IsReachable(*block)) return false;
 
   Instruction* merge_inst = block->GetMergeInst();
   const bool pred_is_header = IsHeader(block);
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 03afe6e..74f3878 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -1034,5 +1034,11 @@
 
   return true;
 }
+
+bool IRContext::IsReachable(const opt::BasicBlock& bb) {
+  auto enclosing_function = bb.GetParent();
+  return GetDominatorAnalysis(enclosing_function)
+      ->Dominates(enclosing_function->entry().get(), &bb);
+}
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index aab3516..f5f38a6 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -598,10 +598,14 @@
   bool ProcessCallTreeFromRoots(ProcessFunction& pfn,
                                 std::queue<uint32_t>* roots);
 
-  // Emmits a error message to the message consumer indicating the error
+  // Emits a error message to the message consumer indicating the error
   // described by |message| occurred in |inst|.
   void EmitErrorMessage(std::string message, Instruction* inst);
 
+  // Returns true if and only if there is a path to |bb| from the entry block of
+  // the function that contains |bb|.
+  bool IsReachable(const opt::BasicBlock& bb);
+
  private:
   // Builds the def-use manager from scratch, even if it was already valid.
   void BuildDefUseManager() {
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index 022c978..850af45 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -27,10 +27,8 @@
 
 bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
   // Is the loop header reachable?
-  return loop_construct_header_->GetLabel()
-      ->context()
-      ->GetDominatorAnalysis(loop_construct_header_->GetParent())
-      ->IsReachable(loop_construct_header_);
+  return loop_construct_header_->GetLabel()->context()->IsReachable(
+      *loop_construct_header_);
 }
 
 void StructuredLoopToSelectionReductionOpportunity::Apply() {
@@ -78,8 +76,7 @@
     }
     already_seen.insert(pred);
 
-    if (!context_->GetDominatorAnalysis(loop_construct_header_->GetParent())
-             ->IsReachable(pred)) {
+    if (!context_->IsReachable(*context_->cfg()->block(pred))) {
       // We do not care about unreachable predecessors (and dominance
       // information, and thus the notion of structured control flow, makes
       // little sense for unreachable blocks).