Add "split block" transformation. (#2633)

With this pass, the fuzzer can split blocks in the input module.  This
is mainly useful in order to give other (future) transformations more
opportunities to apply.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index af25904..a4cbf8e 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -30,17 +30,23 @@
         fuzzer.h
         fuzzer_context.h
         fuzzer_pass.h
+        fuzzer_pass_split_blocks.h
+        fuzzer_util.h
         protobufs/spirvfuzz_protobufs.h
         pseudo_random_generator.h
         random_generator.h
+        transformation_split_block.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
         fact_manager.cpp
         fuzzer.cpp
         fuzzer_context.cpp
         fuzzer_pass.cpp
+        fuzzer_pass_split_blocks.cpp
+        fuzzer_util.cpp
         pseudo_random_generator.cpp
         random_generator.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 2f28d79..559602c 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_split_blocks.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/pseudo_random_generator.h"
 #include "source/opt/build_module.h"
@@ -50,7 +51,8 @@
 Fuzzer::FuzzerResultStatus Fuzzer::Run(
     const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
-    std::vector<uint32_t>* binary_out, protobufs::TransformationSequence*,
+    std::vector<uint32_t>* binary_out,
+    protobufs::TransformationSequence* transformation_sequence_out,
     spv_const_fuzzer_options options) const {
   // Check compatibility between the library version being linked with and the
   // header files being used.
@@ -95,9 +97,10 @@
     return Fuzzer::FuzzerResultStatus::kInitialFactsInvalid;
   }
 
-  // Fuzzer passes will be created and applied here and will populate the
-  // output sequence of transformations.  Currently there are no passes.
-  // TODO(afd) Implement fuzzer passes and invoke them here.
+  // Apply some semantics-preserving passes.
+  FuzzerPassSplitBlocks(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 11de612..959f25a 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -17,9 +17,20 @@
 namespace spvtools {
 namespace fuzz {
 
+namespace {
+// Default probabilities for applying various transformations.
+// All values are percentages.
+// Keep them in alphabetical order.
+
+const uint32_t kDefaultChanceOfSplittingBlock = 20;
+
+}  // namespace
+
 FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
                              uint32_t min_fresh_id)
-    : random_generator_(random_generator), next_fresh_id_(min_fresh_id) {}
+    : random_generator_(random_generator),
+      next_fresh_id_(min_fresh_id),
+      chance_of_splitting_block_(kDefaultChanceOfSplittingBlock) {}
 
 FuzzerContext::~FuzzerContext() = default;
 
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 5ab0f6f..fbd9c45 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -39,11 +39,19 @@
   // or to have been issued before.
   uint32_t GetFreshId();
 
+  // Probabilities associated with applying various transformations.
+  // Keep them in alphabetical order.
+  uint32_t GetChanceOfSplittingBlock() { return chance_of_splitting_block_; }
+
  private:
   // The source of randomness.
   RandomGenerator* random_generator_;
   // The next fresh id to be issued.
   uint32_t next_fresh_id_;
+
+  // Probabilities associated with applying various transformations.
+  // Keep them in alphabetical order.
+  uint32_t chance_of_splitting_block_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_split_blocks.cpp b/source/fuzz/fuzzer_pass_split_blocks.cpp
new file mode 100644
index 0000000..9d400c8
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_split_blocks.cpp
@@ -0,0 +1,91 @@
+// 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_split_blocks.h"
+
+#include <utility>
+#include <vector>
+
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassSplitBlocks::FuzzerPassSplitBlocks(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassSplitBlocks::~FuzzerPassSplitBlocks() = default;
+
+void FuzzerPassSplitBlocks::Apply() {
+  // Consider each block in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // Probabilistically decide whether to try to split this block.
+      if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() >
+          GetFuzzerContext()->GetChanceOfSplittingBlock()) {
+        continue;
+      }
+      // We are going to try to split this block.  We now need to choose where
+      // to split it.  We do this by finding a base instruction that has a
+      // result id, and an offset from that base instruction.  We would like
+      // offsets to be as small as possible and ideally 0 - we only need offsets
+      // because not all instructions can be identified by a result id (e.g.
+      // OpStore instructions cannot).
+      std::vector<std::pair<uint32_t, uint32_t>> base_offset_pairs;
+      // The initial base instruction is the block label.
+      uint32_t base = block.id();
+      uint32_t offset = 0;
+      // Consider every instruction in the block.  The label is excluded: it is
+      // only necessary to consider it as a base in case the first instruction
+      // in the block does not have a result id.
+      for (auto& inst : block) {
+        if (inst.HasResultId()) {
+          // In the case that the instruction has a result id, we use the
+          // instruction as its own base, with zero offset.
+          base = inst.result_id();
+          offset = 0;
+        } else {
+          // The instruction does not have a result id, so we need to identify
+          // it via the latest instruction that did have a result id (base), and
+          // an incremented offset.
+          offset++;
+        }
+        base_offset_pairs.emplace_back(base, offset);
+      }
+      // Having identified all the places we might be able to split the block,
+      // we choose one of them.
+      auto base_offset = base_offset_pairs
+          [GetFuzzerContext()->GetRandomGenerator()->RandomUint32(
+              static_cast<uint32_t>(base_offset_pairs.size()))];
+      auto message = transformation::MakeTransformationSplitBlock(
+          base_offset.first, base_offset.second,
+          GetFuzzerContext()->GetFreshId());
+      // If the position we have chosen turns out to be a valid place to split
+      // the block, we apply the split. Otherwise the block just doesn't get
+      // split.
+      if (transformation::IsApplicable(message, GetIRContext(),
+                                       *GetFactManager())) {
+        transformation::Apply(message, GetIRContext(), GetFactManager());
+        *GetTransformations()->add_transformation()->mutable_split_block() =
+            message;
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h
new file mode 100644
index 0000000..951022b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_split_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_SPLIT_BLOCKS_
+#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for splitting blocks in the module, to create more blocks; this
+// can be very useful for giving other passes a chance to apply.
+class FuzzerPassSplitBlocks : public FuzzerPass {
+ public:
+  FuzzerPassSplitBlocks(opt::IRContext* ir_context, FactManager* fact_manager,
+                        FuzzerContext* fuzzer_context,
+                        protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassSplitBlocks() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // #define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
new file mode 100644
index 0000000..645c121
--- /dev/null
+++ b/source/fuzz/fuzzer_util.cpp
@@ -0,0 +1,36 @@
+// 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_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace fuzzerutil {
+
+bool IsFreshId(opt::IRContext* context, uint32_t id) {
+  return !context->get_def_use_mgr()->GetDef(id);
+}
+
+void UpdateModuleIdBound(opt::IRContext* context, uint32_t id) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the
+  //  case where the maximum id bound is reached.
+  context->module()->SetIdBound(
+      std::max(context->module()->id_bound(), id + 1));
+}
+
+}  // namespace fuzzerutil
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
new file mode 100644
index 0000000..30d870b
--- /dev/null
+++ b/source/fuzz/fuzzer_util.h
@@ -0,0 +1,38 @@
+// 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_UTIL_H_
+#define SOURCE_FUZZ_FUZZER_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Provides global utility methods for use by the fuzzer
+namespace fuzzerutil {
+
+// Returns true if and only if the module does not define the given id.
+bool IsFreshId(opt::IRContext* context, uint32_t id);
+
+// Updates the module's id bound if needed so that it is large enough to
+// account for the given id.
+void UpdateModuleIdBound(opt::IRContext* context, uint32_t id);
+
+}  // namespace fuzzerutil
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_UTIL_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index e87819a..8f1975b 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -34,5 +34,31 @@
 }
 
 message Transformation {
+  oneof transformation {
+    TransformationSplitBlock split_block = 1;
+  }
   // Currently there are no transformations.
 }
+
+message TransformationSplitBlock {
+
+  // A transformation that splits a basic block into two basic blocks.
+
+  // The result id of an instruction.
+  uint32 result_id = 1;
+
+  // An offset, such that the block containing |result_id_| should be split
+  // right before the instruction |offset_| instructions after |result_id_|.
+  uint32 offset = 2;
+
+  // An id that must not yet be used by the module to which this transformation
+  // is applied.  Rather than having the transformation choose a suitable id on
+  // application, we require the id to be given upfront in order to facilitate
+  // reducing fuzzed shaders by removing transformations.  The reason is that
+  // future transformations may refer to the fresh id introduced by this
+  // transformation, and if we end up changing what that id is, due to removing
+  // earlier transformations, it may inhibit later transformations from
+  // applying.
+  uint32 fresh_id = 3;
+
+}
diff --git a/source/fuzz/pseudo_random_generator.cpp b/source/fuzz/pseudo_random_generator.cpp
index 773b89d..9643264 100644
--- a/source/fuzz/pseudo_random_generator.cpp
+++ b/source/fuzz/pseudo_random_generator.cpp
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <cassert>
-
 #include "source/fuzz/pseudo_random_generator.h"
 
+#include <cassert>
+
 namespace spvtools {
 namespace fuzz {
 
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
new file mode 100644
index 0000000..943bed0
--- /dev/null
+++ b/source/fuzz/transformation_split_block.cpp
@@ -0,0 +1,183 @@
+// 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_split_block.h"
+
+#include <utility>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace transformation {
+
+using opt::BasicBlock;
+using opt::IRContext;
+using opt::Instruction;
+using opt::Operand;
+
+namespace {
+
+// Returns:
+// - (true, block->end()) if the relevant instruction is in this block
+//      but inapplicable
+// - (true, it) if 'it' is an iterator for the relevant instruction
+// - (false, _) otherwise.
+std::pair<bool, BasicBlock::iterator> FindInstToSplitBefore(
+    const protobufs::TransformationSplitBlock& message, BasicBlock* block) {
+  // There are three possibilities:
+  // (1) the transformation wants to split at some offset from the block's
+  //     label.
+  // (2) the transformation wants to split at some offset from a
+  //     non-label instruction inside the block.
+  // (3) the split associated with this transformation has nothing to do with
+  //     this block
+  if (message.result_id() == block->id()) {
+    // Case (1).
+    if (message.offset() == 0) {
+      // The offset is not allowed to be 0: this would mean splitting before the
+      // block's label.
+      // By returning (true, block->end()), we indicate that we did find the
+      // instruction (so that it is not worth searching further for it), but
+      // that splitting will not be possible.
+      return {true, block->end()};
+    }
+    // Conceptually, the first instruction in the block is [label + 1].
+    // We thus start from 1 when applying the offset.
+    auto inst_it = block->begin();
+    for (uint32_t i = 1; i < message.offset() && inst_it != block->end(); i++) {
+      ++inst_it;
+    }
+    // This is either the desired instruction, or the end of the block.
+    return {true, inst_it};
+  }
+  for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
+    if (message.result_id() == inst_it->result_id()) {
+      // Case (2): we have found the base instruction; we now apply the offset.
+      for (uint32_t i = 0; i < message.offset() && inst_it != block->end();
+           i++) {
+        ++inst_it;
+      }
+      // This is either the desired instruction, or the end of the block.
+      return {true, inst_it};
+    }
+  }
+  // Case (3).
+  return {false, block->end()};
+}
+
+}  // namespace
+
+bool IsApplicable(const protobufs::TransformationSplitBlock& message,
+                  IRContext* context, const FactManager& /*unused*/) {
+  if (!fuzzerutil::IsFreshId(context, message.fresh_id())) {
+    // We require the id for the new block to be unused.
+    return false;
+  }
+  // Consider every block in every function.
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      auto maybe_split_before = FindInstToSplitBefore(message, &block);
+      if (!maybe_split_before.first) {
+        continue;
+      }
+      if (maybe_split_before.second == block.end()) {
+        // The base instruction was found, but the offset was inappropriate.
+        return false;
+      }
+      if (block.IsLoopHeader()) {
+        // We cannot split a loop header block: back-edges would become invalid.
+        return false;
+      }
+      auto split_before = maybe_split_before.second;
+      if (split_before->PreviousNode() &&
+          split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) {
+        // We cannot split directly after a selection merge: this would separate
+        // the merge from its associated branch or switch operation.
+        return false;
+      }
+      if (split_before->opcode() == SpvOpVariable) {
+        // We cannot split directly after a variable; variables in a function
+        // must be contiguous in the entry block.
+        return false;
+      }
+      if (split_before->opcode() == SpvOpPhi &&
+          split_before->NumInOperands() != 2) {
+        // We cannot split before an OpPhi unless the OpPhi has exactly one
+        // associated incoming edge.
+        return false;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+void Apply(const protobufs::TransformationSplitBlock& message,
+           IRContext* context, FactManager* /*unused*/) {
+  for (auto& function : *context->module()) {
+    for (auto& block : function) {
+      auto maybe_split_before = FindInstToSplitBefore(message, &block);
+      if (!maybe_split_before.first) {
+        continue;
+      }
+      assert(maybe_split_before.second != block.end() &&
+             "If the transformation is applicable, we should have an "
+             "instruction to split on.");
+      // We need to make sure the module's id bound is large enough to add the
+      // fresh id.
+      fuzzerutil::UpdateModuleIdBound(context, message.fresh_id());
+      // Split the block.
+      auto new_bb = block.SplitBasicBlock(context, message.fresh_id(),
+                                          maybe_split_before.second);
+      // The split does not automatically add a branch between the two parts of
+      // the original block, so we add one.
+      block.AddInstruction(MakeUnique<Instruction>(
+          context, SpvOpBranch, 0, 0,
+          std::initializer_list<Operand>{Operand(
+              spv_operand_type_t::SPV_OPERAND_TYPE_ID, {message.fresh_id()})}));
+      // If we split before OpPhi instructions, we need to update their
+      // predecessor operand so that the block they used to be inside is now the
+      // predecessor.
+      new_bb->ForEachPhiInst([&block](Instruction* phi_inst) {
+        // The following assertion is a sanity check.  It is guaranteed to hold
+        // if IsApplicable holds.
+        assert(phi_inst->NumInOperands() == 2 &&
+               "We can only split a block before an OpPhi if block has exactly "
+               "one predecessor.");
+        phi_inst->SetInOperand(1, {block.id()});
+      });
+      // Invalidate all analyses
+      context->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+      return;
+    }
+  }
+  assert(0 &&
+         "Should be unreachable: it should have been possible to apply this "
+         "transformation.");
+}
+
+protobufs::TransformationSplitBlock MakeTransformationSplitBlock(
+    uint32_t result_id, uint32_t offset, uint32_t fresh_id) {
+  protobufs::TransformationSplitBlock result;
+  result.set_result_id(result_id);
+  result.set_offset(offset);
+  result.set_fresh_id(fresh_id);
+  return result;
+}
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
new file mode 100644
index 0000000..9ae419b
--- /dev/null
+++ b/source/fuzz/transformation_split_block.h
@@ -0,0 +1,52 @@
+// 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_SPLIT_BLOCK_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_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 {
+
+// - |result_id| must be the result id of an instruction 'base' in some
+//   block 'blk'.
+// - 'blk' must contain an instruction 'inst' located |offset| instructions
+//   after 'inst' (if |offset| = 0 then 'inst' = 'base').
+// - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards
+//   appear in a new block that 'blk' directly jumps to must be valid.
+// - |fresh_id| must not be used by the module.
+bool IsApplicable(const protobufs::TransformationSplitBlock& message,
+                  opt::IRContext* context, const FactManager& fact_manager);
+
+// - A new block with label |fresh_id| is inserted right after 'blk' in
+//   program order.
+// - All instructions of 'blk' from 'inst' onwards are moved into the new
+//   block.
+// - 'blk' is made to jump unconditionally to the new block.
+void Apply(const protobufs::TransformationSplitBlock& message,
+           opt::IRContext* context, FactManager* fact_manager);
+
+// Creates a protobuf message representing a block-splitting transformation.
+protobufs::TransformationSplitBlock MakeTransformationSplitBlock(
+    uint32_t result_id, uint32_t offset, uint32_t fresh_id);
+
+}  // namespace transformation
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SPLIT_BLOCK_H_
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 5def389..3dca430 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -186,6 +186,7 @@
 add_subdirectory(link)
 add_subdirectory(opt)
 add_subdirectory(reduce)
+add_subdirectory(fuzz)
 add_subdirectory(tools)
 add_subdirectory(util)
 add_subdirectory(val)
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..9165026
--- /dev/null
+++ b/test/fuzz/CMakeLists.txt
@@ -0,0 +1,27 @@
+# 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.
+
+if (${SPIRV_BUILD_FUZZER})
+
+  set(SOURCES
+          fuzz_test_util.h
+
+          fuzz_test_util.cpp
+          transformation_split_block_test.cpp)
+
+  add_spvtools_unittest(TARGET fuzz
+        SRCS ${SOURCES}
+        LIBS SPIRV-Tools-fuzz
+        )
+endif()
diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp
new file mode 100644
index 0000000..545512f
--- /dev/null
+++ b/test/fuzz/fuzz_test_util.cpp
@@ -0,0 +1,89 @@
+// 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 "test/fuzz/fuzz_test_util.h"
+
+#include <iostream>
+
+namespace spvtools {
+namespace fuzz {
+
+bool IsEqual(const spv_target_env env,
+             const std::vector<uint32_t>& expected_binary,
+             const std::vector<uint32_t>& actual_binary) {
+  if (expected_binary == actual_binary) {
+    return true;
+  }
+  SpirvTools t(env);
+  std::string expected_disassembled;
+  std::string actual_disassembled;
+  if (!t.Disassemble(expected_binary, &expected_disassembled,
+                     kFuzzDisassembleOption)) {
+    return false;
+  }
+  if (!t.Disassemble(actual_binary, &actual_disassembled,
+                     kFuzzDisassembleOption)) {
+    return false;
+  }
+  // Using expect gives us a string diff if the strings are not the same.
+  EXPECT_EQ(expected_disassembled, actual_disassembled);
+  // We then return the result of the equality comparison, to be used by an
+  // assertion in the test root function.
+  return expected_disassembled == actual_disassembled;
+}
+
+bool IsEqual(const spv_target_env env, const std::string& expected_text,
+             const std::vector<uint32_t>& actual_binary) {
+  std::vector<uint32_t> expected_binary;
+  SpirvTools t(env);
+  if (!t.Assemble(expected_text, &expected_binary, kFuzzAssembleOption)) {
+    return false;
+  }
+  return IsEqual(env, expected_binary, actual_binary);
+}
+
+bool IsEqual(const spv_target_env env, const std::string& expected_text,
+             const opt::IRContext* actual_ir) {
+  std::vector<uint32_t> actual_binary;
+  actual_ir->module()->ToBinary(&actual_binary, false);
+  return IsEqual(env, expected_text, actual_binary);
+}
+
+bool IsEqual(const spv_target_env env, const opt::IRContext* ir_1,
+             const opt::IRContext* ir_2) {
+  std::vector<uint32_t> binary_1;
+  ir_1->module()->ToBinary(&binary_1, false);
+  std::vector<uint32_t> binary_2;
+  ir_2->module()->ToBinary(&binary_2, false);
+  return IsEqual(env, binary_1, binary_2);
+}
+
+bool IsValid(spv_target_env env, const opt::IRContext* ir) {
+  std::vector<uint32_t> binary;
+  ir->module()->ToBinary(&binary, false);
+  SpirvTools t(env);
+  return t.Validate(binary);
+}
+
+std::string ToString(spv_target_env env, const opt::IRContext* ir) {
+  std::vector<uint32_t> binary;
+  ir->module()->ToBinary(&binary, false);
+  SpirvTools t(env);
+  std::string result;
+  t.Disassemble(binary, &result, kFuzzDisassembleOption);
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h
new file mode 100644
index 0000000..202622c
--- /dev/null
+++ b/test/fuzz/fuzz_test_util.h
@@ -0,0 +1,65 @@
+// 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 TEST_FUZZ_FUZZ_TEST_UTIL_H_
+#define TEST_FUZZ_FUZZ_TEST_UTIL_H_
+
+#include "gtest/gtest.h"
+
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Returns true if and only if the given binaries are bit-wise equal.
+bool IsEqual(spv_target_env env, const std::vector<uint32_t>& expected_binary,
+             const std::vector<uint32_t>& actual_binary);
+
+// Assembles the given text and returns true if and only if the resulting binary
+// is bit-wise equal to the given binary.
+bool IsEqual(spv_target_env env, const std::string& expected_text,
+             const std::vector<uint32_t>& actual_binary);
+
+// Assembles the given text and turns the given IR into binary, then returns
+// true if and only if the resulting binaries are bit-wise equal.
+bool IsEqual(spv_target_env env, const std::string& expected_text,
+             const opt::IRContext* actual_ir);
+
+// Turns the given IRs into binaries, then returns true if and only if the
+// resulting binaries are bit-wise equal.
+bool IsEqual(spv_target_env env, const opt::IRContext* ir_1,
+             const opt::IRContext* ir_2);
+
+// Assembles the given IR context and returns true if and only if
+// the resulting binary is valid.
+bool IsValid(spv_target_env env, const opt::IRContext* ir);
+
+// Assembles the given IR context, then returns its disassembly as a string.
+// Useful for debugging.
+std::string ToString(spv_target_env env, const opt::IRContext* ir);
+
+// Assembly options for writing fuzzer tests.  It simplifies matters if
+// numeric ids do not change.
+const uint32_t kFuzzAssembleOption =
+    SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS;
+// Disassembly options for writing fuzzer tests.
+const uint32_t kFuzzDisassembleOption =
+    SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // TEST_FUZZ_FUZZ_TEST_UTIL_H_
diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp
new file mode 100644
index 0000000..976e81a
--- /dev/null
+++ b/test/fuzz/transformation_split_block_test.cpp
@@ -0,0 +1,773 @@
+// 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_split_block.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSplitBlockTest, NotApplicable) {
+  // The SPIR-V in this test came from the following fragment shader, with
+  // local store elimination applied to get some OpPhi instructions.
+  //
+  // void main() {
+  //   int x;
+  //   int i;
+  //   for (i = 0; i < 100; i++) {
+  //     x += i;
+  //   }
+  // }
+
+  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 "i"
+               OpName %19 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %24 = OpConstant %6 1
+         %28 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+         %27 = OpPhi %6 %28 %5 %22 %13
+         %26 = OpPhi %6 %9 %5 %25 %13
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %18 = OpSLessThan %17 %26 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %22 = OpIAdd %6 %27 %26
+               OpStore %19 %22
+               OpBranch %13
+         %13 = OpLabel
+         %25 = OpIAdd %6 %26 %24
+               OpStore %8 %25
+               OpBranch %10
+         %12 = 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;
+
+  // No split before OpVariable
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(8, 0, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(8, 1, 100), context.get(),
+      fact_manager));
+
+  // No split before OpLabel
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(14, 0, 100), context.get(),
+      fact_manager));
+
+  // No split if base instruction is outside a function
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(1, 0, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(1, 4, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(1, 35, 100), context.get(),
+      fact_manager));
+
+  // No split if block is loop header
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(27, 0, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(27, 1, 100), context.get(),
+      fact_manager));
+
+  // No split if base instruction does not exist
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(88, 0, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(88, 22, 100), context.get(),
+      fact_manager));
+
+  // No split if offset is too large (goes into another block)
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(18, 3, 100), context.get(),
+      fact_manager));
+
+  // No split if id in use
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(18, 0, 27), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(18, 0, 14), context.get(),
+      fact_manager));
+}
+
+TEST(TransformationSplitBlockTest, SplitBlockSeveralTimes) {
+  // The SPIR-V in this test came from the following fragment shader:
+  //
+  // void main() {
+  //   int a;
+  //   int b;
+  //   a = 1;
+  //   b = a;
+  //   a = b;
+  //   b = 2;
+  //   b++;
+  // }
+
+  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 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               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 split_1 = transformation::MakeTransformationSplitBlock(5, 3, 100);
+  ASSERT_TRUE(
+      transformation::IsApplicable(split_1, context.get(), fact_manager));
+  transformation::Apply(split_1, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split_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 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split_1, context.get()));
+
+  auto split_2 = transformation::MakeTransformationSplitBlock(11, 1, 101);
+  ASSERT_TRUE(
+      transformation::IsApplicable(split_2, context.get(), fact_manager));
+  transformation::Apply(split_2, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split_2 = 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 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpBranch %101
+        %101 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split_2, context.get()));
+
+  auto split_3 = transformation::MakeTransformationSplitBlock(14, 0, 102);
+  ASSERT_TRUE(
+      transformation::IsApplicable(split_3, context.get(), fact_manager));
+  transformation::Apply(split_3, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split_3 = 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 "a"
+               OpName %10 "b"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+               OpBranch %101
+        %101 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %6 %10
+               OpStore %8 %12
+               OpStore %10 %13
+               OpBranch %102
+        %102 = OpLabel
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %14 %9
+               OpStore %10 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split_3, context.get()));
+}
+
+TEST(TransformationSplitBlockTest, SplitBlockBeforeSelectBranch) {
+  // The SPIR-V in this test came from the following fragment shader:
+  //
+  // void main() {
+  //   int x, y;
+  //   x = 2;
+  //   if (x < y) {
+  //     y = 3;
+  //   } else {
+  //     y = 4;
+  //   }
+  // }
+
+  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"
+               OpName %11 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %13 = OpTypeBool
+         %17 = OpConstant %6 3
+         %19 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %18
+         %15 = OpLabel
+               OpStore %11 %17
+               OpBranch %16
+         %18 = OpLabel
+               OpStore %11 %19
+               OpBranch %16
+         %16 = 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;
+
+  // Illegal to split between the merge and the conditional branch.
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(14, 2, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(12, 3, 100), context.get(),
+      fact_manager));
+
+  auto split = transformation::MakeTransformationSplitBlock(14, 1, 100);
+  ASSERT_TRUE(transformation::IsApplicable(split, context.get(), fact_manager));
+  transformation::Apply(split, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split = 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 %11 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+               OpDecorate %12 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %13 = OpTypeBool
+         %17 = OpConstant %6 3
+         %19 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %11 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %12 = OpLoad %6 %11
+         %14 = OpSLessThan %13 %10 %12
+               OpBranch %100
+        %100 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %18
+         %15 = OpLabel
+               OpStore %11 %17
+               OpBranch %16
+         %18 = OpLabel
+               OpStore %11 %19
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split, context.get()));
+}
+
+TEST(TransformationSplitBlockTest, SplitBlockBeforeSwitchBranch) {
+  // The SPIR-V in this test came from the following fragment shader:
+  //
+  // void main() {
+  //   int x, y;
+  //   switch (y) {
+  //     case 1:
+  //       x = 2;
+  //     case 2:
+  //       break;
+  //     case 3:
+  //       x = 4;
+  //     default:
+  //       x = 6;
+  //   }
+  // }
+
+  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 "y"
+               OpName %15 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %16 = OpConstant %6 2
+         %18 = OpConstant %6 4
+         %19 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %15 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+               OpSelectionMerge %14 None
+               OpSwitch %9 %13 1 %10 2 %11 3 %12
+         %13 = OpLabel
+               OpStore %15 %19
+               OpBranch %14
+         %10 = OpLabel
+               OpStore %15 %16
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %14
+         %12 = OpLabel
+               OpStore %15 %18
+               OpBranch %13
+         %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;
+
+  // Illegal to split between the merge and the conditional branch.
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(9, 2, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(15, 3, 100), context.get(),
+      fact_manager));
+
+  auto split = transformation::MakeTransformationSplitBlock(9, 1, 100);
+  ASSERT_TRUE(transformation::IsApplicable(split, context.get(), fact_manager));
+  transformation::Apply(split, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split = 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 "y"
+               OpName %15 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+               OpDecorate %15 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %16 = OpConstant %6 2
+         %18 = OpConstant %6 4
+         %19 = OpConstant %6 6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %15 = OpVariable %7 Function
+          %9 = OpLoad %6 %8
+               OpBranch %100
+        %100 = OpLabel
+               OpSelectionMerge %14 None
+               OpSwitch %9 %13 1 %10 2 %11 3 %12
+         %13 = OpLabel
+               OpStore %15 %19
+               OpBranch %14
+         %10 = OpLabel
+               OpStore %15 %16
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %14
+         %12 = OpLabel
+               OpStore %15 %18
+               OpBranch %13
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split, context.get()));
+}
+
+TEST(TransformationSplitBlockTest, NoSplitDuringOpPhis) {
+  // The SPIR-V in this test came from the following fragment shader, with
+  // local store elimination applied to get some OpPhi instructions.
+  //
+  // void main() {
+  //   int x;
+  //   int i;
+  //   for (i = 0; i < 100; i++) {
+  //     x += i;
+  //   }
+  // }
+
+  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 "i"
+               OpName %19 "x"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %25 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %27 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %24 = OpConstant %6 1
+         %28 = OpUndef %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %19 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+         %27 = OpPhi %6 %28 %5 %22 %13
+         %26 = OpPhi %6 %9 %5 %25 %13
+               OpBranch %50
+         %50 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %18 = OpSLessThan %17 %26 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %22 = OpIAdd %6 %27 %26
+               OpStore %19 %22
+               OpBranch %13
+         %13 = OpLabel
+         %25 = OpIAdd %6 %26 %24
+               OpStore %8 %25
+               OpBranch %50
+         %12 = 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;
+
+  // We cannot split before OpPhi instructions, since the number of incoming
+  // blocks may not appropriately match after splitting.
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(26, 0, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(27, 0, 100), context.get(),
+      fact_manager));
+  ASSERT_FALSE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(27, 1, 100), context.get(),
+      fact_manager));
+}
+
+TEST(TransformationSplitBlockTest, SplitOpPhiWithSinglePredecessor) {
+  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"
+               OpName %10 "y"
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+	       OpBranch %20
+	 %20 = OpLabel
+	 %21 = OpPhi %6 %11 %5
+         OpStore %10 %21
+         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;
+
+  ASSERT_TRUE(transformation::IsApplicable(
+      transformation::MakeTransformationSplitBlock(21, 0, 100), context.get(),
+      fact_manager));
+  auto split = transformation::MakeTransformationSplitBlock(20, 1, 100);
+  ASSERT_TRUE(transformation::IsApplicable(split, context.get(), fact_manager));
+  transformation::Apply(split, context.get(), &fact_manager);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_split = 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 %10 RelaxedPrecision
+               OpDecorate %11 RelaxedPrecision
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+	       OpBranch %20
+	 %20 = OpLabel
+         OpBranch %100
+  %100 = OpLabel
+	 %21 = OpPhi %6 %11 %20
+         OpStore %10 %21
+         OpReturn
+         OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_split, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools