spirv-reduce: Eliminate skeletal structured control flow construct (#4360)

This change allows spriv-reduce to get rid of a selection, switch or
loop construct if none of the instructions defined in the construct
are used outside the construct.
diff --git a/BUILD.gn b/BUILD.gn
index 9587f6a..996905a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -803,6 +803,10 @@
     "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h",
     "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp",
     "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h",
+    "source/reduce/structured_construct_to_block_reduction_opportunity.cpp",
+    "source/reduce/structured_construct_to_block_reduction_opportunity.h",
+    "source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp",
+    "source/reduce/structured_construct_to_block_reduction_opportunity_finder.h",
     "source/reduce/structured_loop_to_selection_reduction_opportunity.cpp",
     "source/reduce/structured_loop_to_selection_reduction_opportunity.h",
     "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp",
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 3043243..6fd8409 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -38,6 +38,8 @@
         remove_unused_struct_member_reduction_opportunity_finder.h
         simple_conditional_branch_to_branch_opportunity_finder.h
         simple_conditional_branch_to_branch_reduction_opportunity.h
+        structured_construct_to_block_reduction_opportunity.h
+        structured_construct_to_block_reduction_opportunity_finder.h
         structured_loop_to_selection_reduction_opportunity.h
         structured_loop_to_selection_reduction_opportunity_finder.h
 
@@ -67,6 +69,8 @@
         remove_unused_struct_member_reduction_opportunity_finder.cpp
         simple_conditional_branch_to_branch_opportunity_finder.cpp
         simple_conditional_branch_to_branch_reduction_opportunity.cpp
+        structured_construct_to_block_reduction_opportunity.cpp
+        structured_construct_to_block_reduction_opportunity_finder.cpp
         structured_loop_to_selection_reduction_opportunity.cpp
         structured_loop_to_selection_reduction_opportunity_finder.cpp
 )
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 16bb94f..b752f41 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -28,6 +28,7 @@
 #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
 #include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_construct_to_block_reduction_opportunity_finder.h"
 #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
 #include "source/spirv_reducer_options.h"
 
@@ -113,6 +114,8 @@
   AddReductionPass(
       spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
   AddReductionPass(spvtools::MakeUnique<
+                   StructuredConstructToBlockReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
                    StructuredLoopToSelectionReductionOpportunityFinder>());
   AddReductionPass(
       spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
@@ -141,7 +144,7 @@
     std::unique_ptr<ReductionOpportunityFinder> finder) {
   passes_.push_back(
       spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
-} 
+}
 
 void Reducer::AddCleanupReductionPass(
     std::unique_ptr<ReductionOpportunityFinder> finder) {
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity.cpp b/source/reduce/structured_construct_to_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..ed73841
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity.cpp
@@ -0,0 +1,67 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// 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/structured_construct_to_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool StructuredConstructToBlockReductionOpportunity::PreconditionHolds() {
+  return context_->get_def_use_mgr()->GetDef(construct_header_) != nullptr;
+}
+
+void StructuredConstructToBlockReductionOpportunity::Apply() {
+  auto header_block = context_->cfg()->block(construct_header_);
+  auto merge_block = context_->cfg()->block(header_block->MergeBlockId());
+
+  auto* enclosing_function = header_block->GetParent();
+
+  // A region of blocks is defined in terms of dominators and post-dominators,
+  // so we compute these for the enclosing function.
+  auto* dominators = context_->GetDominatorAnalysis(enclosing_function);
+  auto* postdominators = context_->GetPostDominatorAnalysis(enclosing_function);
+
+  // For each block in the function, determine whether it is inside the region.
+  // If it is, delete it.
+  for (auto block_it = enclosing_function->begin();
+       block_it != enclosing_function->end();) {
+    if (header_block != &*block_it && merge_block != &*block_it &&
+        dominators->Dominates(header_block, &*block_it) &&
+        postdominators->Dominates(merge_block, &*block_it)) {
+      block_it = block_it.Erase();
+    } else {
+      ++block_it;
+    }
+  }
+  // Having removed some blocks from the module it is necessary to invalidate
+  // analyses, since the remaining patch-up work depends on various analyses
+  // which will otherwise reference blocks that have been deleted.
+  context_->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // We demote the header of the region to a regular block by deleting its merge
+  // instruction.
+  context_->KillInst(header_block->GetMergeInst());
+
+  // The terminator for the header block is changed to be an unconditional
+  // branch to the merge block.
+  header_block->terminator()->SetOpcode(SpvOpBranch);
+  header_block->terminator()->SetInOperands(
+      {{SPV_OPERAND_TYPE_ID, {merge_block->id()}}});
+
+  // This is an intrusive change, so we invalidate all analyses.
+  context_->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity.h b/source/reduce/structured_construct_to_block_reduction_opportunity.h
new file mode 100644
index 0000000..f461a2f
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// 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_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to replace a skeletal structured control flow construct with a
+// single block.
+class StructuredConstructToBlockReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity from a header block id.
+  StructuredConstructToBlockReductionOpportunity(opt::IRContext* context,
+                                                 uint32_t construct_header)
+      : context_(context), construct_header_(construct_header) {}
+
+  // Returns true if and only if |construct_header_| exists in the module -
+  // another opportunity may have removed it.
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  uint32_t construct_header_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..dc20f68
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,185 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// 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/structured_construct_to_block_reduction_opportunity_finder.h"
+
+#include <unordered_set>
+
+#include "source/reduce/structured_construct_to_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+StructuredConstructToBlockReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context, uint32_t target_function) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every function in the module.
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    // For every header block in the function, there is potentially a region of
+    // blocks that could be collapsed.
+    std::unordered_map<opt::BasicBlock*, std::unordered_set<opt::BasicBlock*>>
+        regions;
+
+    // Regions are identified using dominators and postdominators, so we compute
+    // those for the function.
+    auto* dominators = context->GetDominatorAnalysis(function);
+    auto* postdominators = context->GetPostDominatorAnalysis(function);
+
+    // Consider every block in the function.
+    for (auto& block : *function) {
+      // If a block has an unreachable predecessor then folding away a region in
+      // which that block is contained gets complicated, so we ignore regions
+      // that contain such blocks. We note whether this block suffers from this
+      // problem.
+      bool has_unreachable_predecessor =
+          HasUnreachablePredecessor(block, context);
+
+      // Look through all the regions we have identified so far to see whether
+      // this block is part of a region, or spoils a region (by having an
+      // unreachable predecessor).
+      for (auto entry = regions.begin(); entry != regions.end();) {
+        // |block| is in this region if it is dominated by the header,
+        // post-dominated by the merge, and different from the merge.
+        assert(&block != entry->first &&
+               "The block should not be the region's header because we only "
+               "make a region when we encounter its header.");
+        if (entry->first->MergeBlockId() != block.id() &&
+            dominators->Dominates(entry->first, &block) &&
+            postdominators->Dominates(
+                entry->first->GetMergeInst()->GetSingleWordInOperand(0),
+                block.id())) {
+          if (has_unreachable_predecessor) {
+            // The block would be in this region, but it has an unreachable
+            // predecessor. This spoils the region, so we remove it.
+            entry = regions.erase(entry);
+            continue;
+          } else {
+            // Add the block to the region.
+            entry->second.insert(&block);
+          }
+        }
+        ++entry;
+      }
+      if (block.MergeBlockIdIfAny() == 0) {
+        // The block isn't a header, so it doesn't constitute a new region.
+        continue;
+      }
+      if (!context->IsReachable(block)) {
+        // The block isn't reachable, so it doesn't constitute a new region.
+        continue;
+      }
+      auto* merge_block = context->cfg()->block(
+          block.GetMergeInst()->GetSingleWordInOperand(0));
+      if (!context->IsReachable(*merge_block)) {
+        // The block's merge is unreachable, so it doesn't constitute a new
+        // region.
+        continue;
+      }
+      assert(dominators->Dominates(&block, merge_block) &&
+             "The merge block is reachable, so the header must dominate it");
+      if (!postdominators->Dominates(merge_block, &block)) {
+        // The block is not post-dominated by its merge. This happens for
+        // instance when there is a break from a conditional, or an early exit.
+        // This also means that we don't add a region.
+        continue;
+      }
+      // We have a reachable header block with a rechable merge that
+      // postdominates the header: this means we have a new region.
+      regions.emplace(&block, std::unordered_set<opt::BasicBlock*>());
+    }
+
+    // Now that we have found all the regions and blocks within them, we check
+    // whether any region defines an id that is used outside the region. If this
+    // is *not* the case, then we have an opportunity to collapse the region
+    // down to its header block and merge block.
+    for (auto& entry : regions) {
+      if (DefinitionsRestrictedToRegion(*entry.first, entry.second, context)) {
+        result.emplace_back(
+            MakeUnique<StructuredConstructToBlockReductionOpportunity>(
+                context, entry.first->id()));
+      }
+    }
+  }
+  return result;
+}
+
+bool StructuredConstructToBlockReductionOpportunityFinder::
+    DefinitionsRestrictedToRegion(
+        const opt::BasicBlock& header,
+        const std::unordered_set<opt::BasicBlock*>& region,
+        opt::IRContext* context) {
+  // Consider every block in the region.
+  for (auto& block : region) {
+    // Consider every instruction in the block - this includes the label
+    // instruction
+    if (!block->WhileEachInst(
+            [context, &header, &region](opt::Instruction* inst) -> bool {
+              if (inst->result_id() == 0) {
+                // The instruction does not genreate a result id, thus it cannot
+                // be referred to outside the region - this is fine.
+                return true;
+              }
+              // Consider every use of the instruction's result id.
+              if (!context->get_def_use_mgr()->WhileEachUse(
+                      inst->result_id(),
+                      [context, &header, &region](opt::Instruction* user,
+                                                  uint32_t) -> bool {
+                        auto user_block = context->get_instr_block(user);
+                        if (user == header.GetMergeInst() ||
+                            user == header.terminator()) {
+                          // We are going to delete the header's merge
+                          // instruction and rewrite its terminator, so it does
+                          // not matter if the user is one of these
+                          // instructions.
+                          return true;
+                        }
+                        if (user_block == nullptr ||
+                            region.count(user_block) == 0) {
+                          // The user is either a global instruction, or an
+                          // instruction in a block outside the region. Removing
+                          // the region would invalidate this user.
+                          return false;
+                        }
+                        return true;
+                      })) {
+                return false;
+              }
+              return true;
+            })) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool StructuredConstructToBlockReductionOpportunityFinder::
+    HasUnreachablePredecessor(const opt::BasicBlock& block,
+                              opt::IRContext* context) {
+  for (auto pred : context->cfg()->preds(block.id())) {
+    if (!context->IsReachable(*context->cfg()->block(pred))) {
+      return true;
+    }
+  }
+  return false;
+}
+
+std::string StructuredConstructToBlockReductionOpportunityFinder::GetName()
+    const {
+  return "StructuredConstructToBlockReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity_finder.h b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..28bbc17
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// 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_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H
+#define SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to replace a skeletal structured control flow
+// construct - that is, a construct that does not define anything that's used
+// outside the construct - into its header block.
+class StructuredConstructToBlockReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  StructuredConstructToBlockReductionOpportunityFinder() = default;
+
+  ~StructuredConstructToBlockReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context, uint32_t target_function) const final;
+
+ private:
+  // Returns true if and only if all instructions defined in |region| are used
+  // only inside |region|, with the exception that they may be used by the merge
+  // or terminator instruction of |header|, which must be the header block for
+  // the region.
+  static bool DefinitionsRestrictedToRegion(
+      const opt::BasicBlock& header,
+      const std::unordered_set<opt::BasicBlock*>& region,
+      opt::IRContext* context);
+
+  // Returns true if and only if |block| has at least one predecessor that is
+  // unreachable in the control flow graph of its function.
+  static bool HasUnreachablePredecessor(const opt::BasicBlock& block,
+                                        opt::IRContext* context);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index 652f0ab..121cd4f 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -14,6 +14,7 @@
 
 add_spvtools_unittest(TARGET reduce
         SRCS
+        conditional_branch_to_simple_conditional_branch_test.cpp
         merge_blocks_test.cpp
         operand_to_constant_test.cpp
         operand_to_undef_test.cpp
@@ -26,10 +27,10 @@
         remove_selection_test.cpp
         remove_unused_instruction_test.cpp
         remove_unused_struct_member_test.cpp
+        simple_conditional_branch_to_branch_test.cpp
+        structured_construct_to_block_test.cpp
         structured_loop_to_selection_test.cpp
         validation_during_reduction_test.cpp
-        conditional_branch_to_simple_conditional_branch_test.cpp
-        simple_conditional_branch_to_branch_test.cpp
         LIBS SPIRV-Tools-reduce
         )
 
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
index 0c23411..4271660 100644
--- a/test/reduce/reduce_test_util.cpp
+++ b/test/reduce/reduce_test_util.cpp
@@ -21,6 +21,29 @@
 namespace spvtools {
 namespace reduce {
 
+const spvtools::MessageConsumer kConsoleMessageConsumer =
+    [](spv_message_level_t level, const char*, const spv_position_t& position,
+       const char* message) -> void {
+  switch (level) {
+    case SPV_MSG_FATAL:
+    case SPV_MSG_INTERNAL_ERROR:
+    case SPV_MSG_ERROR:
+      std::cerr << "error: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_WARNING:
+      std::cout << "warning: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    case SPV_MSG_INFO:
+      std::cout << "info: line " << position.index << ": " << message
+                << std::endl;
+      break;
+    default:
+      break;
+  }
+};
+
 void CheckEqual(const spv_target_env env,
                 const std::vector<uint32_t>& expected_binary,
                 const std::vector<uint32_t>& actual_binary) {
@@ -55,8 +78,9 @@
 void CheckValid(spv_target_env env, const opt::IRContext* ir) {
   std::vector<uint32_t> binary;
   ir->module()->ToBinary(&binary, false);
-  SpirvTools t(env);
-  ASSERT_TRUE(t.Validate(binary));
+  SpirvTools tools(env);
+  tools.SetMessageConsumer(kConsoleMessageConsumer);
+  ASSERT_TRUE(tools.Validate(binary));
 }
 
 std::string ToString(spv_target_env env, const opt::IRContext* ir) {
diff --git a/test/reduce/structured_construct_to_block_test.cpp b/test/reduce/structured_construct_to_block_test.cpp
new file mode 100644
index 0000000..9500966
--- /dev/null
+++ b/test/reduce/structured_construct_to_block_test.cpp
@@ -0,0 +1,245 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// 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/structured_construct_to_block_reduction_opportunity_finder.h"
+
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "test/reduce/reduce_test_util.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(StructuredConstructToBlockReductionPassTest, SimpleTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %19 = OpConstant %6 3
+         %29 = OpConstant %6 1
+         %31 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %15
+         %15 = OpLabel
+         %18 = OpLoad %6 %8
+         %20 = OpSGreaterThan %10 %18 %19
+               OpSelectionMerge %22 None
+               OpBranchConditional %20 %21 %22
+         %21 = OpLabel
+               OpBranch %16
+         %22 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %14
+         %16 = OpLabel
+         %24 = OpLoad %6 %8
+               OpSelectionMerge %28 None
+               OpSwitch %24 %27 1 %25 2 %26
+         %27 = OpLabel
+               OpStore %8 %19
+               OpBranch %28
+         %25 = OpLabel
+               OpStore %8 %29
+               OpBranch %28
+         %26 = OpLabel
+               OpStore %8 %31
+               OpBranch %28
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredConstructToBlockReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get(), 0);
+  ASSERT_EQ(3, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  CheckValid(env, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %19 = OpConstant %6 3
+         %29 = OpConstant %6 1
+         %31 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %24 = OpLoad %6 %8
+               OpBranch %28
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredConstructToBlockReductionPassTest, CannotBeRemovedDueToUses) {
+  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 %100 "temp"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %19 = OpConstant %6 3
+         %29 = OpConstant %6 1
+         %31 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+        %100 = OpCopyObject %10 %11
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %15
+         %15 = OpLabel
+         %18 = OpLoad %6 %8
+         %20 = OpSGreaterThan %10 %18 %19
+               OpSelectionMerge %22 None
+               OpBranchConditional %20 %21 %22
+         %21 = OpLabel
+               OpBranch %16
+         %22 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %14
+         %16 = OpLabel
+        %101 = OpCopyObject %6 %18
+         %24 = OpLoad %6 %8
+               OpSelectionMerge %28 None
+               OpSwitch %24 %27 1 %25 2 %26
+         %27 = OpLabel
+               OpStore %8 %19
+        %102 = OpCopyObject %10 %11
+               OpBranch %28
+         %25 = OpLabel
+               OpStore %8 %29
+               OpBranch %28
+         %26 = OpLabel
+               OpStore %8 %31
+               OpBranch %28
+         %28 = OpLabel
+        %103 = OpPhi %10 %102 %27 %11 %25 %11 %26
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredConstructToBlockReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get(), 0);
+  ASSERT_TRUE(ops.empty());
+}
+
+TEST(StructuredConstructToBlockReductionPassTest,
+     CannotBeRemovedDueToOpPhiAtMerge) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+        %101 = OpPhi %10 %11 %5 %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredConstructToBlockReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get(), 0);
+  ASSERT_TRUE(ops.empty());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools