Add spirv-fuzz pass to permute blocks. (#2642)

The blocks within each function in the module will be permuted in a
randomized manner that respects dominance.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index a4cbf8e..6ef1f6a 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -30,11 +30,13 @@
         fuzzer.h
         fuzzer_context.h
         fuzzer_pass.h
+        fuzzer_pass_permute_blocks.h
         fuzzer_pass_split_blocks.h
         fuzzer_util.h
         protobufs/spirvfuzz_protobufs.h
         pseudo_random_generator.h
         random_generator.h
+        transformation_move_block_down.h
         transformation_split_block.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
@@ -42,10 +44,12 @@
         fuzzer.cpp
         fuzzer_context.cpp
         fuzzer_pass.cpp
+        fuzzer_pass_permute_blocks.cpp
         fuzzer_pass_split_blocks.cpp
         fuzzer_util.cpp
         pseudo_random_generator.cpp
         random_generator.cpp
+        transformation_move_block_down.cpp
         transformation_split_block.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
         )
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 559602c..f013070 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -19,6 +19,7 @@
 
 #include "source/fuzz/fact_manager.h"
 #include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_permute_blocks.h"
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/pseudo_random_generator.h"
@@ -102,6 +103,13 @@
                         transformation_sequence_out)
       .Apply();
 
+  // TODO(afd) Various other passes will be added.
+
+  // Finally, give the blocks in the module a good shake-up.
+  FuzzerPassPermuteBlocks(ir_context.get(), &fact_manager, &fuzzer_context,
+                          transformation_sequence_out)
+      .Apply();
+
   // Encode the module as a binary.
   ir_context->module()->ToBinary(binary_out, false);
 
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 959f25a..c2ffb62 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -22,6 +22,7 @@
 // All values are percentages.
 // Keep them in alphabetical order.
 
+const uint32_t kDefaultChanceOfMovingBlockDown = 25;
 const uint32_t kDefaultChanceOfSplittingBlock = 20;
 
 }  // namespace
@@ -30,6 +31,7 @@
                              uint32_t min_fresh_id)
     : random_generator_(random_generator),
       next_fresh_id_(min_fresh_id),
+      chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown),
       chance_of_splitting_block_(kDefaultChanceOfSplittingBlock) {}
 
 FuzzerContext::~FuzzerContext() = default;
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index fbd9c45..2c6bf9c 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -42,6 +42,7 @@
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
   uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
+  uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
 
  private:
   // The source of randomness.
@@ -51,6 +52,7 @@
 
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
+  uint32_t chance_of_moving_block_down_;
   uint32_t chance_of_splitting_block_;
 };
 
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.cpp b/source/fuzz/fuzzer_pass_permute_blocks.cpp
new file mode 100644
index 0000000..34131dc
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_blocks.cpp
@@ -0,0 +1,85 @@
+// 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/fuzz/fuzzer_pass_permute_blocks.h"
+
+#include "source/fuzz/transformation_move_block_down.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteBlocks::FuzzerPassPermuteBlocks(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassPermuteBlocks::~FuzzerPassPermuteBlocks() = default;
+
+void FuzzerPassPermuteBlocks::Apply() {
+  // For now we do something very simple: we randomly decide whether to move a
+  // block, and for each block that we do move, we push it down as far as we
+  // legally can.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2635): it would be
+  //  nice to randomly sample from the set of legal block permutations and then
+  //  encode the chosen permutation via a series of move-block-down
+  //  transformations.  This should be possible but will require some thought.
+
+  for (auto& function : *GetIRContext()->module()) {
+    std::vector<uint32_t> block_ids;
+    // Collect all block ids for the function before messing with block
+    // ordering.
+    for (auto& block : function) {
+      block_ids.push_back(block.id());
+    }
+    // Now consider each block id.  We consider block ids in reverse, because
+    // e.g. in code generated from the following:
+    //
+    // if (...) {
+    //   A
+    //   B
+    // } else {
+    //   C
+    // }
+    //
+    // block A cannot be moved down, but B has freedom to move and that movement
+    // would provide more freedom for A to move.
+    for (auto id = block_ids.rbegin(); id != block_ids.rend(); ++id) {
+      // Randomly decide whether to ignore the block id.
+      if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+          GetFuzzerContext()->GetChanceOfMovingBlockDown()) {
+        continue;
+      }
+      // Keep pushing the block down, until pushing down fails.
+      // The loop is guaranteed to terminate because a block cannot be pushed
+      // down indefinitely.
+      while (true) {
+        protobufs::TransformationMoveBlockDown message;
+        message.set_block_id(*id);
+        if (transformation::IsApplicable(message, GetIRContext(),
+                                         *GetFactManager())) {
+          transformation::Apply(message, GetIRContext(), GetFactManager());
+          *GetTransformations()
+               ->add_transformation()
+               ->mutable_move_block_down() = message;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.h b/source/fuzz/fuzzer_pass_permute_blocks.h
new file mode 100644
index 0000000..d8aed72
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_blocks.h
@@ -0,0 +1,39 @@
+// 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_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for shuffling the blocks of the module in a validity-preserving
+// manner.
+class FuzzerPassPermuteBlocks : public FuzzerPass {
+ public:
+  FuzzerPassPermuteBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+                          FuzzerContext* fuzzer_context,
+                          protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPermuteBlocks() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 8f1975b..c760b02 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -35,11 +35,20 @@
 
 message Transformation {
   oneof transformation {
-    TransformationSplitBlock split_block = 1;
+    TransformationMoveBlockDown move_block_down = 1;
+    TransformationSplitBlock split_block = 2;
   }
   // Currently there are no transformations.
 }
 
+message TransformationMoveBlockDown {
+  // A transformation that moves a basic block to be one position lower in
+  // program order.
+
+  // The id of the block to move down.
+  uint32 block_id = 1;
+}
+
 message TransformationSplitBlock {
 
   // A transformation that splits a basic block into two basic blocks.
diff --git a/source/fuzz/transformation_move_block_down.cpp b/source/fuzz/transformation_move_block_down.cpp
new file mode 100644
index 0000000..858af74
--- /dev/null
+++ b/source/fuzz/transformation_move_block_down.cpp
@@ -0,0 +1,102 @@
+// 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/fuzz/transformation_move_block_down.h"
+
+#include "source/opt/basic_block.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+using opt::BasicBlock;
+using opt::IRContext;
+
+bool IsApplicable(const protobufs::TransformationMoveBlockDown& message,
+                  IRContext* context, const FactManager& /*unused*/) {
+  // Go through every block in every function, looking for a block whose id
+  // matches that of the block we want to consider moving down.
+  for (auto& function : *context->module()) {
+    for (auto block_it = function.begin(); block_it != function.end();
+         ++block_it) {
+      if (block_it->id() == message.block_id()) {
+        // We have found a match.
+        if (block_it == function.begin()) {
+          // The block is the first one appearing in the function.  We are not
+          // allowed to move this block down.
+          return false;
+        }
+        // Record the block we would like to consider moving down.
+        BasicBlock* block_matching_id = &*block_it;
+        // Now see whether there is some block following that block in program
+        // order.
+        ++block_it;
+        if (block_it == function.end()) {
+          // There is no such block; i.e., the block we are considering moving
+          // is the last one in the function.  The transformation thus does not
+          // apply.
+          return false;
+        }
+        BasicBlock* next_block_in_program_order = &*block_it;
+        // We can move the block of interest down if and only if it does not
+        // dominate the block that comes next.
+        return !context->GetDominatorAnalysis(&function)->Dominates(
+            block_matching_id, next_block_in_program_order);
+      }
+    }
+  }
+
+  // We did not find a matching block, so the transformation is not applicable:
+  // there is no relevant block to move.
+  return false;
+}
+
+void Apply(const protobufs::TransformationMoveBlockDown& message,
+           IRContext* context, FactManager* /*unused*/) {
+  // Go through every block in every function, looking for a block whose id
+  // matches that of the block we want to move down.
+  for (auto& function : *context->module()) {
+    for (auto block_it = function.begin(); block_it != function.end();
+         ++block_it) {
+      if (block_it->id() == message.block_id()) {
+        ++block_it;
+        assert(block_it != function.end() &&
+               "To be able to move a block down, it needs to have a "
+               "program-order successor.");
+        function.MoveBasicBlockToAfter(message.block_id(), &*block_it);
+        // It is prudent to invalidate analyses after changing block ordering in
+        // case any of them depend on it, but the ones that definitely do not
+        // depend on ordering can be preserved. These include the following,
+        // which can likely be extended.
+        context->InvalidateAnalysesExceptFor(
+            IRContext::Analysis::kAnalysisDefUse |
+            IRContext::Analysis::kAnalysisDominatorAnalysis);
+
+        return;
+      }
+    }
+  }
+  assert(false && "No block was found to move down.");
+}
+
+protobufs::TransformationMoveBlockDown MakeTransformationMoveBlockDown(
+    uint32_t id) {
+  protobufs::TransformationMoveBlockDown result;
+  result.set_block_id(id);
+  return result;
+}
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_move_block_down.h b/source/fuzz/transformation_move_block_down.h
new file mode 100644
index 0000000..072ad53
--- /dev/null
+++ b/source/fuzz/transformation_move_block_down.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_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
+#define SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+// - |block_id| must be the id of a block b in the given module.
+// - b must not be the first nor last block appearing, in program order,
+//   in a function.
+// - b must not dominate the block that follows it in program order.
+bool IsApplicable(const protobufs::TransformationMoveBlockDown& message,
+                  opt::IRContext* context, const FactManager& fact_manager);
+
+// The block with id |block_id| is moved down; i.e. the program order
+// between it and the block that follows it is swapped.
+void Apply(const protobufs::TransformationMoveBlockDown& message,
+           opt::IRContext* context, FactManager* fact_manager);
+
+// Creates a protobuf message to move down the block with id |id|.
+protobufs::TransformationMoveBlockDown MakeTransformationMoveBlockDown(
+    uint32_t id);
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MOVE_BLOCK_DOWN_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 9165026..62ea641 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -18,6 +18,7 @@
           fuzz_test_util.h
 
           fuzz_test_util.cpp
+          transformation_move_block_down_test.cpp
           transformation_split_block_test.cpp)
 
   add_spvtools_unittest(TARGET fuzz
diff --git a/test/fuzz/transformation_move_block_down_test.cpp b/test/fuzz/transformation_move_block_down_test.cpp
new file mode 100644
index 0000000..122d007
--- /dev/null
+++ b/test/fuzz/transformation_move_block_down_test.cpp
@@ -0,0 +1,830 @@
+// 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/fuzz/transformation_move_block_down.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible1) {
+  // Block 11 cannot be moved down as it dominates block 12.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %8 %9
+               OpBranch %12
+         %12 = OpLabel
+               OpStore %8 %10
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = transformation::MakeTransformationMoveBlockDown(11);
+  ASSERT_FALSE(transformation::IsApplicable(transformation, context.get(),
+                                            fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible2) {
+  // Block 5 cannot be moved down as it is the entry block.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %8 %10
+               OpReturn
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = transformation::MakeTransformationMoveBlockDown(5);
+  ASSERT_FALSE(transformation::IsApplicable(transformation, context.get(),
+                                            fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible3) {
+  // Block 100 does not exist, so cannot be moved down.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %8 %9
+               OpBranch %12
+         %12 = OpLabel
+               OpStore %8 %10
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = transformation::MakeTransformationMoveBlockDown(100);
+  ASSERT_FALSE(transformation::IsApplicable(transformation, context.get(),
+                                            fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, NoMovePossible4) {
+  // Block 12 is the last block in its function, so cannot be moved down.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %11
+         %11 = OpLabel
+               OpStore %8 %9
+               OpBranch %12
+         %12 = OpLabel
+               OpStore %8 %10
+               OpReturn
+               OpFunctionEnd
+         %13 = OpFunction %2 None %3
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = transformation::MakeTransformationMoveBlockDown(12);
+  ASSERT_FALSE(transformation::IsApplicable(transformation, context.get(),
+                                            fact_manager));
+}
+
+TEST(TransformationMoveBlockDownTest, MovePossible) {
+  // Block 11 can be moved down as it does not dominate block 12 (both are
+  // unreachable).
+  std::string before_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %8 %10
+               OpReturn
+         %11 = OpLabel
+               OpReturn
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %8 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %10 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %8 %10
+               OpReturn
+         %12 = OpLabel
+               OpReturn
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, before_transformation, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  auto transformation = transformation::MakeTransformationMoveBlockDown(11);
+  ASSERT_TRUE(transformation::IsApplicable(transformation, context.get(),
+                                           fact_manager));
+  transformation::Apply(transformation, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMoveBlockDownTest, ManyMovesPossible) {
+  // The SPIR-V arising from this shader has lots of opportunities for moving
+  // blocks around.
+  //
+  // void main() {
+  //   int x;
+  //   int y;
+  //   if (x < y) {
+  //     x = 1;
+  //     if (y == x) {
+  //       x = 3;
+  //     } else {
+  //       x = 4;
+  //     }
+  //   } else {
+  //     if (y < x) {
+  //       x = 5;
+  //     } else {
+  //       x = 6;
+  //     }
+  //   }
+  // }
+
+  std::string before_transformation = 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"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %12 = OpTypeBool
+         %16 = OpConstant %6 1
+         %22 = OpConstant %6 3
+         %24 = OpConstant %6 4
+         %31 = OpConstant %6 5
+         %33 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+         %11 = OpLoad %6 %10
+         %13 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %25
+         %14 = OpLabel
+               OpStore %8 %16
+         %17 = OpLoad %6 %10
+         %18 = OpLoad %6 %8
+         %19 = OpIEqual %12 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %19 %20 %23
+         %20 = OpLabel
+               OpStore %8 %22
+               OpBranch %21
+         %23 = OpLabel
+               OpStore %8 %24
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %15
+         %25 = OpLabel
+         %26 = OpLoad %6 %10
+         %27 = OpLoad %6 %8
+         %28 = OpSLessThan %12 %26 %27
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %32
+         %29 = OpLabel
+               OpStore %8 %31
+               OpBranch %30
+         %32 = OpLabel
+               OpStore %8 %33
+               OpBranch %30
+         %30 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, before_transformation, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+
+  // The block ids are: 5 14 20 23 21 25 29 32 30 15
+  // We make a transformation to move each of them down, plus a transformation
+  // to move a non-block, 27, down.
+  auto move_down_5 = transformation::MakeTransformationMoveBlockDown(5);
+  auto move_down_14 = transformation::MakeTransformationMoveBlockDown(14);
+  auto move_down_20 = transformation::MakeTransformationMoveBlockDown(20);
+  auto move_down_23 = transformation::MakeTransformationMoveBlockDown(23);
+  auto move_down_21 = transformation::MakeTransformationMoveBlockDown(21);
+  auto move_down_25 = transformation::MakeTransformationMoveBlockDown(25);
+  auto move_down_29 = transformation::MakeTransformationMoveBlockDown(29);
+  auto move_down_32 = transformation::MakeTransformationMoveBlockDown(32);
+  auto move_down_30 = transformation::MakeTransformationMoveBlockDown(30);
+  auto move_down_15 = transformation::MakeTransformationMoveBlockDown(15);
+  auto move_down_27 = transformation::MakeTransformationMoveBlockDown(27);
+
+  // Dominance is as follows:
+  //  5 dominates everything else
+  // 14 dominates 20, 23, 21
+  // 20 dominates nothing
+  // 23 dominates nothing
+  // 21 dominates nothing
+  // 25 dominates 29, 32, 30
+  // 29 dominates nothing
+  // 32 dominates nothing
+  // 30 dominates nothing
+  // 15 dominates nothing
+
+  // Current ordering: 5 14 20 23 21 25 29 32 30 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  // Let's bubble 20 all the way down.
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 20 21 25 29 32 30 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 20 25 29 32 30 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 20 29 32 30 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 20 32 30 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 32 20 30 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 32 30 20 15
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+
+  transformation::Apply(move_down_20, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_bubbling_20_down = 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"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %12 = OpTypeBool
+         %16 = OpConstant %6 1
+         %22 = OpConstant %6 3
+         %24 = OpConstant %6 4
+         %31 = OpConstant %6 5
+         %33 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+         %11 = OpLoad %6 %10
+         %13 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %25
+         %14 = OpLabel
+               OpStore %8 %16
+         %17 = OpLoad %6 %10
+         %18 = OpLoad %6 %8
+         %19 = OpIEqual %12 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %19 %20 %23
+         %23 = OpLabel
+               OpStore %8 %24
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %15
+         %25 = OpLabel
+         %26 = OpLoad %6 %10
+         %27 = OpLoad %6 %8
+         %28 = OpSLessThan %12 %26 %27
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %32
+         %29 = OpLabel
+               OpStore %8 %31
+               OpBranch %30
+         %32 = OpLabel
+               OpStore %8 %33
+               OpBranch %30
+         %30 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpStore %8 %22
+               OpBranch %21
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_bubbling_20_down, context.get()));
+
+  // Current ordering: 5 14 23 21 25 29 32 30 15 20
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+
+  transformation::Apply(move_down_23, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 21 23 25 29 32 30 15 20
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+
+  transformation::Apply(move_down_23, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 21 25 23 29 32 30 15 20
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+
+  transformation::Apply(move_down_21, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Current ordering: 5 14 25 21 23 29 32 30 15 20
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+
+  transformation::Apply(move_down_14, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_more_shuffling = 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"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %17 RelaxedPrecision
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %12 = OpTypeBool
+         %16 = OpConstant %6 1
+         %22 = OpConstant %6 3
+         %24 = OpConstant %6 4
+         %31 = OpConstant %6 5
+         %33 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+         %11 = OpLoad %6 %10
+         %13 = OpSLessThan %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %25
+         %25 = OpLabel
+         %26 = OpLoad %6 %10
+         %27 = OpLoad %6 %8
+         %28 = OpSLessThan %12 %26 %27
+               OpSelectionMerge %30 None
+               OpBranchConditional %28 %29 %32
+         %14 = OpLabel
+               OpStore %8 %16
+         %17 = OpLoad %6 %10
+         %18 = OpLoad %6 %8
+         %19 = OpIEqual %12 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %19 %20 %23
+         %21 = OpLabel
+               OpBranch %15
+         %23 = OpLabel
+               OpStore %8 %24
+               OpBranch %21
+         %29 = OpLabel
+               OpStore %8 %31
+               OpBranch %30
+         %32 = OpLabel
+               OpStore %8 %33
+               OpBranch %30
+         %30 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpStore %8 %22
+               OpBranch %21
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_more_shuffling, context.get()));
+
+  // Final ordering: 5 25 14 21 23 29 32 30 15 20
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_5, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_25, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_14, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_21, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_23, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_29, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_32, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_30, context.get(), fact_manager));
+  ASSERT_TRUE(
+      transformation::IsApplicable(move_down_15, context.get(), fact_manager));
+  ASSERT_FALSE(
+      transformation::IsApplicable(move_down_20, context.get(), fact_manager));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools