reduce: remove unreferenced blocks pass (#2398)

diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index 1a6ead4..4680231 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -24,6 +24,8 @@
         reduction_opportunity_finder.h
         reduction_pass.h
         reduction_util.h
+        remove_block_reduction_opportunity.h
+        remove_block_reduction_opportunity_finder.h
         remove_instruction_reduction_opportunity.h
         remove_function_reduction_opportunity.h
         remove_function_reduction_opportunity_finder.h
@@ -43,6 +45,8 @@
         reduction_opportunity.cpp
         reduction_pass.cpp
         reduction_util.cpp
+        remove_block_reduction_opportunity.cpp
+        remove_block_reduction_opportunity_finder.cpp
         remove_function_reduction_opportunity.cpp
         remove_function_reduction_opportunity_finder.cpp
         remove_instruction_reduction_opportunity.cpp
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..9b952c4
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2019 Google LLC
+//
+// 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 "remove_block_reduction_opportunity.h"
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
+    Function* function, BasicBlock* block)
+    : function_(function), block_(block) {
+  // precondition:
+  assert(block_->begin() != block_->end() &&
+         block_->begin()->context()->get_def_use_mgr()->NumUsers(
+             block_->id()) == 0 &&
+         "RemoveBlockReductionOpportunity block must have 0 references");
+}
+
+bool RemoveBlockReductionOpportunity::PreconditionHolds() {
+  // Removing other blocks cannot disable this opportunity.
+  return true;
+}
+
+void RemoveBlockReductionOpportunity::Apply() {
+  // We need an iterator pointing to the block, hence the loop.
+  for (auto bi = function_->begin(); bi != function_->end(); ++bi) {
+    if (bi->id() == block_->id()) {
+      bi->KillAllInsts(true);
+      bi.Erase();
+      // Block removal changes the function, but we don't use analyses, so no
+      // need to invalidate them.
+      return;
+    }
+  }
+
+  assert(false &&
+         "Unreachable: we should have found a block with the desired id.");
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h
new file mode 100644
index 0000000..45c0c17
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// 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_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "reduction_opportunity.h"
+#include "source/opt/basic_block.h"
+#include "source/opt/function.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to remove an unreferenced block.
+// See RemoveBlockReductionOpportunityFinder.
+class RemoveBlockReductionOpportunity : public ReductionOpportunity {
+ public:
+  // Creates the opportunity to remove |block| in |function| in |context|.
+  RemoveBlockReductionOpportunity(opt::Function* function,
+                                  opt::BasicBlock* block);
+
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::Function* function_;
+  opt::BasicBlock* block_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..a4ca1f2
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,96 @@
+// Copyright (c) 2019 Google LLC
+//
+// 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/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+using namespace opt;
+
+std::string RemoveBlockReductionOpportunityFinder::GetName() const {
+  return "RemoveBlockReductionOpportunityFinder";
+}
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto bi = function.begin(); bi != function.end(); ++bi) {
+      if (IsBlockValidOpportunity(context, function, bi)) {
+        result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
+            &function, &*bi));
+      }
+    }
+  }
+  return result;
+}
+
+bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
+    opt::IRContext* context, opt::Function& function,
+    opt::Function::iterator& bi) {
+  assert(bi != function.end() && "Block iterator was out of bounds");
+
+  // Don't remove first block; we don't want to end up with no blocks.
+  if (bi == function.begin()) {
+    return false;
+  }
+
+  // Don't remove blocks with references.
+  if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+    return false;
+  }
+
+  // Don't remove blocks whose instructions have outside references.
+  if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool RemoveBlockReductionOpportunityFinder::
+    BlockInstructionsHaveNoOutsideReferences(opt::IRContext* context,
+                                             const Function::iterator& bi) {
+  // Get all instructions in block.
+  std::unordered_set<uint32_t> instructions_in_block;
+  for (const Instruction& instruction : *bi) {
+    instructions_in_block.insert(instruction.unique_id());
+  }
+
+  // For each instruction...
+  for (const Instruction& instruction : *bi) {
+    // For each use of the instruction...
+    bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
+        &instruction, [&instructions_in_block](Instruction* user) -> bool {
+          // If the use is in this block, continue (return true). Otherwise, we
+          // found an outside use; return false (and stop).
+          return instructions_in_block.find(user->unique_id()) !=
+                 instructions_in_block.end();
+        });
+
+    if (!no_uses_outside_block) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..83cd04b
--- /dev/null
+++ b/source/reduce/remove_block_reduction_opportunity_finder.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2019 Google LLC
+//
+// 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_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+#define SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
+
+#include "source/opt/function.h"
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder of opportunities to remove a block. The optimizer can remove dead
+// code. However, the reducer needs to be able to remove at a fine-grained
+// level.
+class RemoveBlockReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  RemoveBlockReductionOpportunityFinder() = default;
+
+  ~RemoveBlockReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context) const final;
+
+ private:
+  // Returns true if the block |bi| in function |function| is a valid
+  // opportunity according to various restrictions.
+  static bool IsBlockValidOpportunity(opt::IRContext* context,
+                                      opt::Function& function,
+                                      opt::Function::iterator& bi);
+
+  // Returns true if the instructions (definitions) in block |bi| have no
+  // references, except for references from inside the block itself.
+  static bool BlockInstructionsHaveNoOutsideReferences(
+      opt::IRContext* context, const opt::Function::iterator& bi);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_REMOVE_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H_
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index df9542b..4acab56 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -20,6 +20,7 @@
         reduce_test_util.cpp
         reduce_test_util.h
         reducer_test.cpp
+        remove_block_test.cpp
         remove_function_test.cpp
         remove_opname_instruction_test.cpp
         remove_unreferenced_instruction_test.cpp
diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp
new file mode 100644
index 0000000..71508c8
--- /dev/null
+++ b/test/reduce/remove_block_test.cpp
@@ -0,0 +1,358 @@
+// Copyright (c) 2019 Google LLC
+//
+// 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 "reduce_test_util.h"
+#include "source/opt/build_module.h"
+#include "source/reduce/reduction_opportunity.h"
+#include "source/reduce/remove_block_reduction_opportunity.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+namespace {
+
+TEST(RemoveBlockReductionPassTest, BasicCheck) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %13 = OpLabel ; unreachable
+               OpStore %8 %9
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %15 = OpLabel ; unreachable
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = 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 =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(2, 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
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %15 = OpLabel
+               OpStore %8 %11
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               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
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+         %11 = OpConstant %6 3
+         %12 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %14
+         %14 = OpLabel
+               OpStore %8 %10
+               OpBranch %16
+         %16 = OpLabel
+               OpStore %8 %12
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_1, context.get());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableContinueAndMerge) {
+  // Loop with unreachable merge and continue target. There should be no
+  // opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %16 %15 None
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+         %15 = OpLabel
+               OpBranch %13
+         %16 = 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 =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, OneBlock) {
+  // Function with just one block. There should be no opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = 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 =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithOutsideIdUses) {
+  // A function with two unreachable blocks A -> B. A defines ID %9 and B uses
+  // %9. There are no references to A, but removing A would be invalid because
+  // of B's use of %9, so there should be no opportunities.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+          %8 = OpLabel          ; A
+          %9 = OpUndef %4
+               OpBranch %10
+         %10 = OpLabel          ; B
+         %11 = OpIAdd %4 %6 %9  ; uses %9 from A, so A cannot be removed
+               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 =
+      RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get());
+  ASSERT_EQ(0, ops.size());
+}
+
+TEST(RemoveBlockReductionPassTest, UnreachableBlocksWithInsideIdUses) {
+  // Similar to the above test.
+
+  // A function with two unreachable blocks A -> B. Both blocks create and use
+  // IDs, but the uses are contained within each block, so A should be removed.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+          %8 = OpLabel                     ; A
+          %9 = OpUndef %4                  ; define %9
+         %10 = OpIAdd %4 %6 %9             ; use %9
+               OpBranch %11
+         %11 = OpLabel                     ; B
+         %12 = OpUndef %4                  ; define %12
+         %13 = OpIAdd %4 %6 %12            ; use %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+  auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+
+  ops[0]->TryToApply();
+
+  // Same as above, but block A is removed.
+  std::string after_op_0 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+         %11 = OpLabel
+         %12 = OpUndef %4
+         %13 = OpIAdd %4 %6 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0, context.get());
+
+  // Find opportunities again. There are no reference to B. B should now be
+  // removed.
+
+  ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
+      context.get());
+
+  ASSERT_EQ(1, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+
+  ops[0]->TryToApply();
+
+  // Same as above, but block B is removed.
+  std::string after_op_0_again = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeFunction %3
+          %6 = OpConstant %4 1
+          %2 = OpFunction %3 None %5
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after_op_0_again, context.get());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools