spirv-reduce: Allow merging unreachable blocks (#4303)

This change allows the reducer to merge together blocks even when they
are unreachable, but keeps the restriction of reachability in place
for the optimizer.

Fixes #4302.
diff --git a/source/fuzz/transformation_merge_blocks.cpp b/source/fuzz/transformation_merge_blocks.cpp
index 2223679..dbf782e 100644
--- a/source/fuzz/transformation_merge_blocks.cpp
+++ b/source/fuzz/transformation_merge_blocks.cpp
@@ -43,6 +43,9 @@
   }
   auto first_block = ir_context->cfg()->block(predecessors.at(0));
 
+  if (!ir_context->IsReachable(*first_block)) {
+    return false;
+  }
   return opt::blockmergeutil::CanMergeWithSuccessor(ir_context, first_block);
 }
 
diff --git a/source/fuzz/transformation_merge_blocks.h b/source/fuzz/transformation_merge_blocks.h
index f6306c5..f3462fa 100644
--- a/source/fuzz/transformation_merge_blocks.h
+++ b/source/fuzz/transformation_merge_blocks.h
@@ -31,6 +31,7 @@
   TransformationMergeBlocks(uint32_t block_id);
 
   // - |message_.block_id| must be the id of a block, b
+  // - b must be statically reachable in the control flow graph of its function
   // - b must have a single predecessor, a
   // - b must be the sole successor of a
   // - Replacing a with the merge of a and b (and removing b) must lead to a
diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp
index c7315ba..04e47f1 100644
--- a/source/opt/block_merge_pass.cpp
+++ b/source/opt/block_merge_pass.cpp
@@ -28,7 +28,9 @@
 bool BlockMergePass::MergeBlocks(Function* func) {
   bool modified = false;
   for (auto bi = func->begin(); bi != func->end();) {
-    if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
+    // Don't bother trying to merge unreachable blocks.
+    if (context()->IsReachable(*bi) &&
+        blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
       blockmergeutil::MergeWithSuccessor(context(), func, bi);
       // Reprocess block.
       modified = true;
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
index 39b5074..15e8c6f 100644
--- a/source/opt/block_merge_util.cpp
+++ b/source/opt/block_merge_util.cpp
@@ -103,9 +103,6 @@
     return false;
   }
 
-  // Don't bother trying to merge unreachable blocks.
-  if (!context->IsReachable(*block)) return false;
-
   Instruction* merge_inst = block->GetMergeInst();
   const bool pred_is_header = IsHeader(block);
   if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) {
diff --git a/test/reduce/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp
index 8506ee0..c472301 100644
--- a/test/reduce/merge_blocks_test.cpp
+++ b/test/reduce/merge_blocks_test.cpp
@@ -647,6 +647,64 @@
   MergeBlocksReductionPassTest_LoopReturn_Helper(true);
 }
 
+TEST(MergeBlocksReductionPassTest, MergeUnreachable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %11 = OpTypeBool
+         %12 = OpConstantFalse %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+          %9 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  const auto ops =
+      MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get(), 0);
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+
+  std::string after = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %11 = OpTypeBool
+         %12 = OpConstantFalse %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools