spirv-fuzz: Merge the return instructions in a function (#3838)

This PR introduces TransformationMergeFunctionReturns, which changes
a function so that it only has one reachable return statement.

Fixes #3655.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 61c9fb3..ef699a7 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -91,6 +91,7 @@
         fuzzer_pass_interchange_zero_like_constants.h
         fuzzer_pass_make_vector_operations_dynamic.h
         fuzzer_pass_merge_blocks.h
+        fuzzer_pass_merge_function_returns.h
         fuzzer_pass_mutate_pointers.h
         fuzzer_pass_obfuscate_constants.h
         fuzzer_pass_outline_functions.h
@@ -180,6 +181,7 @@
         transformation_load.h
         transformation_make_vector_operation_dynamic.h
         transformation_merge_blocks.h
+        transformation_merge_function_returns.h
         transformation_move_block_down.h
         transformation_move_instruction_down.h
         transformation_mutate_pointer.h
@@ -269,6 +271,7 @@
         fuzzer_pass_interchange_zero_like_constants.cpp
         fuzzer_pass_make_vector_operations_dynamic.cpp
         fuzzer_pass_merge_blocks.cpp
+        fuzzer_pass_merge_function_returns.cpp
         fuzzer_pass_mutate_pointers.cpp
         fuzzer_pass_obfuscate_constants.cpp
         fuzzer_pass_outline_functions.cpp
@@ -356,6 +359,7 @@
         transformation_load.cpp
         transformation_make_vector_operation_dynamic.cpp
         transformation_merge_blocks.cpp
+        transformation_merge_function_returns.cpp
         transformation_move_block_down.cpp
         transformation_move_instruction_down.cpp
         transformation_mutate_pointer.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 37260ca..7d82884 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -60,6 +60,7 @@
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
 #include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
 #include "source/fuzz/fuzzer_pass_merge_blocks.h"
+#include "source/fuzz/fuzzer_pass_merge_function_returns.h"
 #include "source/fuzz/fuzzer_pass_mutate_pointers.h"
 #include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
 #include "source/fuzz/fuzzer_pass_outline_functions.h"
@@ -269,6 +270,7 @@
     MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>(
         &pass_instances);
     MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances);
+    MaybeAddRepeatedPass<FuzzerPassMergeFunctionReturns>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 5151117..34f25cd 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -99,6 +99,7 @@
 const std::pair<uint32_t, uint32_t> kChanceOfMakingVectorOperationDynamic = {
     20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
+const std::pair<uint32_t, uint32_t> kChanceOfMergingFunctionReturns = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfMutatingPointer = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
@@ -277,6 +278,8 @@
   chance_of_making_vector_operation_dynamic_ =
       ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic);
   chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
+  chance_of_merging_function_returns_ =
+      ChooseBetweenMinAndMax(kChanceOfMergingFunctionReturns);
   chance_of_moving_block_down_ =
       ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
   chance_of_mutating_pointer_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 213d51b..4436b2b 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -244,6 +244,9 @@
     return chance_of_making_vector_operation_dynamic_;
   }
   uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
+  uint32_t GetChanceOfMergingFunctionReturns() {
+    return chance_of_merging_function_returns_;
+  }
   uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
   uint32_t GetChanceOfMutatingPointer() { return chance_of_mutating_pointer_; }
   uint32_t GetChanceOfObfuscatingConstant() {
@@ -452,6 +455,7 @@
   uint32_t chance_of_making_donor_livesafe_;
   uint32_t chance_of_making_vector_operation_dynamic_;
   uint32_t chance_of_merging_blocks_;
+  uint32_t chance_of_merging_function_returns_;
   uint32_t chance_of_moving_block_down_;
   uint32_t chance_of_mutating_pointer_;
   uint32_t chance_of_obfuscating_constant_;
diff --git a/source/fuzz/fuzzer_pass_merge_function_returns.cpp b/source/fuzz/fuzzer_pass_merge_function_returns.cpp
new file mode 100644
index 0000000..7130e43
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_merge_function_returns.cpp
@@ -0,0 +1,257 @@
+// Copyright (c) 2020 Stefano Milizia
+//
+// 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_merge_function_returns.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_merge_function_returns.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassMergeFunctionReturns::FuzzerPassMergeFunctionReturns(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassMergeFunctionReturns::~FuzzerPassMergeFunctionReturns() = default;
+
+void FuzzerPassMergeFunctionReturns::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    // Randomly decide whether to consider this function.
+    if (GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfMergingFunctionReturns())) {
+      continue;
+    }
+
+    // Only consider functions that have early returns.
+    if (!function.HasEarlyReturn()) {
+      continue;
+    }
+
+    // Get the return blocks.
+    auto return_blocks = fuzzerutil::GetReachableReturnBlocks(
+        GetIRContext(), function.result_id());
+
+    // Only go ahead if there is more than one reachable return block.
+    if (return_blocks.size() <= 1) {
+      continue;
+    }
+
+    // Make sure that OpConstantTrue and OpConstantFalse are in the module.
+    FindOrCreateBoolConstant(true, false);
+    FindOrCreateBoolConstant(false, false);
+
+    // Collect the ids available after the entry block of the function.
+    auto ids_available_after_entry_block =
+        GetTypesToIdsAvailableAfterEntryBlock(&function);
+
+    // If the entry block does not branch unconditionally to another block,
+    // split it.
+    if (function.entry()->terminator()->opcode() != SpvOpBranch) {
+      SplitBlockAfterOpPhiOrOpVariable(function.entry()->id());
+    }
+
+    // Collect the merge blocks of the function whose corresponding loops
+    // contain return blocks.
+    auto merge_blocks = GetMergeBlocksOfLoopsContainingBlocks(return_blocks);
+
+    // Split the merge blocks, if they contain instructions different from
+    // OpLabel, OpPhi and OpBranch. Collect the new ids of merge blocks.
+    std::vector<uint32_t> actual_merge_blocks;
+    for (uint32_t merge_block : merge_blocks) {
+      opt::BasicBlock* block = GetIRContext()->get_instr_block(merge_block);
+
+      // We don't need to split blocks that are already suitable (they only
+      // contain OpLabel, OpPhi or OpBranch instructions).
+      if (GetIRContext()
+              ->get_instr_block(merge_block)
+              ->WhileEachInst([](opt::Instruction* inst) {
+                return inst->opcode() == SpvOpLabel ||
+                       inst->opcode() == SpvOpPhi ||
+                       inst->opcode() == SpvOpBranch;
+              })) {
+        actual_merge_blocks.emplace_back(merge_block);
+        continue;
+      }
+
+      // If the merge block is also a loop header, we need to add a preheader,
+      // which will be the new merge block.
+      if (block->IsLoopHeader()) {
+        actual_merge_blocks.emplace_back(
+            GetOrCreateSimpleLoopPreheader(merge_block)->id());
+        continue;
+      }
+
+      // If the merge block is not a loop header, we must split it after the
+      // last OpPhi instruction. The merge block will be the first of the pair
+      // of blocks obtained after splitting, and it keeps the original id.
+      SplitBlockAfterOpPhiOrOpVariable(merge_block);
+      actual_merge_blocks.emplace_back(merge_block);
+    }
+
+    // Get the ids needed by the transformation.
+    uint32_t outer_header_id = GetFuzzerContext()->GetFreshId();
+    uint32_t outer_return_id = GetFuzzerContext()->GetFreshId();
+
+    bool function_is_void =
+        GetIRContext()->get_type_mgr()->GetType(function.type_id())->AsVoid();
+
+    // We only need a return value if the function is not void.
+    uint32_t return_val_id =
+        function_is_void ? 0 : GetFuzzerContext()->GetFreshId();
+
+    // We only need a placeholder for the return value if the function is not
+    // void and there is at least one relevant merge block.
+    uint32_t returnable_val_id = 0;
+    if (!function_is_void && !actual_merge_blocks.empty()) {
+      // If there is an id of the suitable type, choose one at random.
+      if (ids_available_after_entry_block.count(function.type_id())) {
+        const auto& candidates =
+            ids_available_after_entry_block[function.type_id()];
+        returnable_val_id =
+            candidates[GetFuzzerContext()->RandomIndex(candidates)];
+      } else {
+        // If there is no id, add a global OpUndef.
+        uint32_t suitable_id = FindOrCreateGlobalUndef(function.type_id());
+        // Add the new id to the map of available ids.
+        ids_available_after_entry_block.emplace(
+            function.type_id(), std::vector<uint32_t>({suitable_id}));
+        returnable_val_id = suitable_id;
+      }
+    }
+
+    // Collect all the ids needed for merge blocks.
+    auto merge_blocks_info = GetInfoNeededForMergeBlocks(
+        actual_merge_blocks, &ids_available_after_entry_block);
+
+    // Apply the transformation if it is applicable (it could be inapplicable if
+    // adding new predecessors to merge blocks breaks dominance rules).
+    MaybeApplyTransformation(TransformationMergeFunctionReturns(
+        function.result_id(), outer_header_id, outer_return_id, return_val_id,
+        returnable_val_id, merge_blocks_info));
+  }
+}
+
+std::map<uint32_t, std::vector<uint32_t>>
+FuzzerPassMergeFunctionReturns::GetTypesToIdsAvailableAfterEntryBlock(
+    opt::Function* function) const {
+  std::map<uint32_t, std::vector<uint32_t>> result;
+  // Consider all global declarations
+  for (auto& global : GetIRContext()->module()->types_values()) {
+    if (global.HasResultId() && global.type_id()) {
+      if (!result.count(global.type_id())) {
+        result.emplace(global.type_id(), std::vector<uint32_t>());
+      }
+      result[global.type_id()].emplace_back(global.result_id());
+    }
+  }
+
+  // Consider all function parameters
+  function->ForEachParam([&result](opt::Instruction* param) {
+    if (param->HasResultId() && param->type_id()) {
+      if (!result.count(param->type_id())) {
+        result.emplace(param->type_id(), std::vector<uint32_t>());
+      }
+
+      result[param->type_id()].emplace_back(param->result_id());
+    }
+  });
+
+  // Consider all the instructions in the entry block.
+  for (auto& inst : *function->entry()) {
+    if (inst.HasResultId() && inst.type_id()) {
+      if (!result.count(inst.type_id())) {
+        result.emplace(inst.type_id(), std::vector<uint32_t>());
+      }
+      result[inst.type_id()].emplace_back(inst.result_id());
+    }
+  }
+
+  return result;
+}
+
+std::set<uint32_t>
+FuzzerPassMergeFunctionReturns::GetMergeBlocksOfLoopsContainingBlocks(
+    const std::set<uint32_t>& blocks) const {
+  std::set<uint32_t> result;
+  for (uint32_t block : blocks) {
+    uint32_t merge_block =
+        GetIRContext()->GetStructuredCFGAnalysis()->LoopMergeBlock(block);
+
+    while (merge_block != 0 && !result.count(merge_block)) {
+      // Add a new entry.
+      result.emplace(merge_block);
+
+      // Walk up the loop tree.
+      block = merge_block;
+      merge_block = GetIRContext()->GetStructuredCFGAnalysis()->LoopMergeBlock(
+          merge_block);
+    }
+  }
+
+  return result;
+}
+
+std::vector<protobufs::ReturnMergingInfo>
+FuzzerPassMergeFunctionReturns::GetInfoNeededForMergeBlocks(
+    const std::vector<uint32_t>& merge_blocks,
+    std::map<uint32_t, std::vector<uint32_t>>*
+        ids_available_after_entry_block) {
+  std::vector<protobufs::ReturnMergingInfo> result;
+  for (uint32_t merge_block : merge_blocks) {
+    protobufs::ReturnMergingInfo info;
+    info.set_merge_block_id(merge_block);
+    info.set_is_returning_id(this->GetFuzzerContext()->GetFreshId());
+    info.set_maybe_return_val_id(this->GetFuzzerContext()->GetFreshId());
+
+    // Add all the ids needed for the OpPhi instructions.
+    this->GetIRContext()
+        ->get_instr_block(merge_block)
+        ->ForEachPhiInst([this, &info, &ids_available_after_entry_block](
+                             opt::Instruction* phi_inst) {
+          protobufs::UInt32Pair entry;
+          entry.set_first(phi_inst->result_id());
+
+          // If there is an id of the suitable type, choose one at random.
+          if (ids_available_after_entry_block->count(phi_inst->type_id())) {
+            auto& candidates =
+                ids_available_after_entry_block->at(phi_inst->type_id());
+            entry.set_second(
+                candidates[this->GetFuzzerContext()->RandomIndex(candidates)]);
+          } else {
+            // If there is no id, add a global OpUndef.
+            uint32_t suitable_id =
+                this->FindOrCreateGlobalUndef(phi_inst->type_id());
+            // Add the new id to the map of available ids.
+            ids_available_after_entry_block->emplace(
+                phi_inst->type_id(), std::vector<uint32_t>({suitable_id}));
+            entry.set_second(suitable_id);
+          }
+
+          // Add the entry to the list.
+          *info.add_opphi_to_suitable_id() = entry;
+        });
+
+    result.emplace_back(info);
+  }
+
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_merge_function_returns.h b/source/fuzz/fuzzer_pass_merge_function_returns.h
new file mode 100644
index 0000000..0fa505c
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_merge_function_returns.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2020 Stefano Milizia
+//
+// 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_MERGE_FUNCTION_RETURNS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_MERGE_FUNCTION_RETURNS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for changing functions in the module so that they don't have an
+// early return.
+class FuzzerPassMergeFunctionReturns : public FuzzerPass {
+ public:
+  FuzzerPassMergeFunctionReturns(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassMergeFunctionReturns();
+
+  void Apply() override;
+
+ private:
+  // Returns a map from type ids to a list of ids with that type and which are
+  // available at the end of the entry block of |function|.
+  std::map<uint32_t, std::vector<uint32_t>>
+  GetTypesToIdsAvailableAfterEntryBlock(opt::Function* function) const;
+
+  // Returns the set of all the loop merge blocks whose corresponding loops
+  // contain at least one of the blocks in |blocks|.
+  std::set<uint32_t> GetMergeBlocksOfLoopsContainingBlocks(
+      const std::set<uint32_t>& blocks) const;
+
+  // Returns a list of ReturnMergingInfo messages, containing the information
+  // needed by the transformation for each of the relevant merge blocks.
+  // If a new id is created (because |ids_available_after_entry_block| does not
+  // have an entry for the corresponding type), a new entry is added to
+  // |ids_available_after_entry_block|, mapping its type to a singleton set
+  // containing it.
+  std::vector<protobufs::ReturnMergingInfo> GetInfoNeededForMergeBlocks(
+      const std::vector<uint32_t>& merge_blocks,
+      std::map<uint32_t, std::vector<uint32_t>>*
+          ids_available_after_entry_block);
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_MERGE_FUNCTION_RETURNS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 7944d23..48aee58 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -437,6 +437,28 @@
   return result;
 }
 
+uint32_t GetLoopFromMergeBlock(opt::IRContext* ir_context,
+                               uint32_t merge_block_id) {
+  uint32_t result = 0;
+  ir_context->get_def_use_mgr()->WhileEachUse(
+      merge_block_id,
+      [ir_context, &result](opt::Instruction* use_instruction,
+                            uint32_t use_index) -> bool {
+        switch (use_instruction->opcode()) {
+          case SpvOpLoopMerge:
+            // The merge block operand is the first operand in OpLoopMerge.
+            if (use_index == 0) {
+              result = ir_context->get_instr_block(use_instruction)->id();
+              return false;
+            }
+            return true;
+          default:
+            return true;
+        }
+      });
+  return result;
+}
+
 uint32_t FindFunctionType(opt::IRContext* ir_context,
                           const std::vector<uint32_t>& type_ids) {
   // Look through the existing types for a match.
@@ -1673,6 +1695,23 @@
   }
 }
 
+std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
+                                            uint32_t function_id) {
+  auto function = ir_context->GetFunction(function_id);
+  assert(function && "The function |function_id| must exist.");
+
+  std::set<uint32_t> result;
+
+  ir_context->cfg()->ForEachBlockInPostOrder(function->entry().get(),
+                                             [&result](opt::BasicBlock* block) {
+                                               if (block->IsReturn()) {
+                                                 result.emplace(block->id());
+                                               }
+                                             });
+
+  return result;
+}
+
 }  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 981d229..c7e55e2 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -164,6 +164,11 @@
 // Returns true if and only if |block_id| is a merge block or continue target
 bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id);
 
+// Returns the id of the header of the loop corresponding to the given loop
+// merge block. Returns 0 if |merge_block_id| is not a loop merge block.
+uint32_t GetLoopFromMergeBlock(opt::IRContext* ir_context,
+                               uint32_t merge_block_id);
+
 // Returns the result id of an instruction of the form:
 //  %id = OpTypeFunction |type_ids|
 // or 0 if no such instruction exists.
@@ -547,6 +552,12 @@
 //  (called using OpExtInst) have not been considered.
 bool InstructionHasNoSideEffects(const opt::Instruction& instruction);
 
+// Returns a set of the ids of all the return blocks that are reachable from
+// the entry block of |function_id|.
+// Assumes that the function exists in the module.
+std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
+                                            uint32_t function_id);
+
 }  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h
index ed55741..51ff2ae 100644
--- a/source/fuzz/pass_management/repeated_pass_instances.h
+++ b/source/fuzz/pass_management/repeated_pass_instances.h
@@ -47,6 +47,7 @@
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
 #include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
 #include "source/fuzz/fuzzer_pass_merge_blocks.h"
+#include "source/fuzz/fuzzer_pass_merge_function_returns.h"
 #include "source/fuzz/fuzzer_pass_mutate_pointers.h"
 #include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
 #include "source/fuzz/fuzzer_pass_outline_functions.h"
@@ -137,6 +138,7 @@
   REPEATED_PASS_INSTANCE(InvertComparisonOperators);
   REPEATED_PASS_INSTANCE(MakeVectorOperationsDynamic);
   REPEATED_PASS_INSTANCE(MergeBlocks);
+  REPEATED_PASS_INSTANCE(MergeFunctionReturns);
   REPEATED_PASS_INSTANCE(MutatePointers);
   REPEATED_PASS_INSTANCE(ObfuscateConstants);
   REPEATED_PASS_INSTANCE(OutlineFunctions);
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
index 8597de2..8894353 100644
--- a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
@@ -188,8 +188,10 @@
   if (&pass == pass_instances_->GetDonateModules()) {
     // - New functions in the module can be called
     // - Donated dead functions produce irrelevant ids, which can be replaced
+    // - Donated functions are good candidates for having their returns merged
     return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
-                                  pass_instances_->GetReplaceIrrelevantIds()});
+                                  pass_instances_->GetReplaceIrrelevantIds(),
+                                  pass_instances_->GetMergeFunctionReturns()});
   }
   if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) {
     // - Parts of duplicated regions can be outlined
@@ -221,6 +223,11 @@
     //   different way
     return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()});
   }
+  if (&pass == pass_instances_->GetMergeFunctionReturns()) {
+    // - Functions without early returns are more likely to be able to be
+    //   inlined.
+    return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions()});
+  }
   if (&pass == pass_instances_->GetMutatePointers()) {
     // - This creates irrelevant ids, which can be replaced
     return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index de2f843..bebe848 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -381,6 +381,58 @@
   uint32 value_to_copy_id = 7;
 }
 
+message ReturnMergingInfo {
+  // TransformationMergeFunctionReturns needs to modify each merge block of
+  // loops containing return instructions, by:
+  // - adding instructions to decide whether the function is returning
+  // - adding instructions to pass on the return value of the function,
+  //   if it is returning
+  // - changing the branch instruction (which must be an unconditional branch)
+  //   to a conditional branch that, if the function is returning, branches to
+  //   the merge block of the innermost loop that contains this merge block
+  //   (which can be the new merge block introduced by the transformation).
+  //
+  // One such merge block of the form:
+  //  %block = OpLabel
+  //   %phi1 = OpPhi %type1 %val1_1 %pred1 %val1_2 %pred2
+  //   %phi2 = OpPhi %type2 %val2_1 %pred1 %val2_2 %pred2
+  //           OpBranch %next
+  //
+  // is transformed into:
+  //               %block = OpLabel
+  //     %is_returning_id = OpPhi %bool %false %pred1 %false %pred2 %true %ret_bb1 %is_bb2_returning %mer_bb2
+  // %maybe_return_val_id = OpPhi %return_type %any_returnable_val %pred1 %any_returnable_val %pred2
+  //                                            %ret_val1 %ret_bb1 %ret_val2 %mer_bb2
+  //                %phi1 = OpPhi %type1 %val1_1 %pred1 %val1_2 %pred2
+  //                                     %any_suitable_id_1 %ret_bb1 %any_suitable_id_1 %mer_bb2
+  //                %phi2 = OpPhi %type2 %val2_1 %pred1 %val2_2 %pred2
+  //                                     %any_suitable_id_1 %ret_bb1 %any_suitable_id_1 %mer_bb2
+  //                        OpBranchConditional %is_returning_id %innermost_loop_merge %next
+  //
+  // where %ret_bb1 is a block that originally contains a return instruction and %mer_bb2 is the merge block of an inner
+  // loop, from where the function might be returning.
+  //
+  // Note that the block is required to only have OpLabel, OpPhi or OpBranch instructions.
+
+  // The id of the merge block that needs to be modified.
+  uint32 merge_block_id = 1;
+
+  // A fresh id for a boolean OpPhi whose value will be true iff the function
+  // is returning. This will be used to decide whether to break out of the loop
+  // or to use the original branch of the function. This value will also be
+  // used by the merge block of the enclosing loop (if there is one) if the
+  // function is returning from this block.
+  uint32 is_returning_id = 2;
+
+  // A fresh id that will get the value being returned, if the function is
+  // returning. If the function return type is void, this is ignored.
+  uint32 maybe_return_val_id = 3;
+
+  // A mapping from each existing OpPhi id to a suitable id of the same type
+  // available to use before the instruction.
+  repeated UInt32Pair opphi_to_suitable_id = 4;
+}
+
 message LoopLimiterInfo {
 
   // Structure capturing the information required to manipulate a loop limiter
@@ -1489,6 +1541,65 @@
 
 }
 
+message TransformationMergeFunctionReturns {
+
+  // A transformation that modifies a function so that it does not return early,
+  // so it only has one return statement (ignoring unreachable blocks).
+  //
+  // The function is enclosed inside an outer loop, that is only executed once,
+  // and whose merge block is the new return block of the function.
+  //
+  // Each return instruction is replaced by:
+  //     OpBranch %innermost_loop_merge
+  // where %innermost_loop_merge is the innermost loop containing the return
+  // instruction.
+  //
+  // Each merge block whose associated loop contains return instructions is
+  // changed so that it branches to the merge block of the loop containing it,
+  // as explained in the comments to the ReturnMergingInfo message.
+  //
+  // The new return block (the merge block of the new outer loop) will be of
+  // the following form (if the return type is not void):
+  //  %outer_return_id = OpLabel
+  //   %return_val_id = OpPhi %return_type %val1 %block_1 %val2 %block_2 ...
+  //                    OpReturnValue %return_val_id
+  // where %block_k is either a return block that, in the original function, is
+  // outside of any loops, or the merge block of a loop that contains return
+  // instructions and is not, originally, nested inside another loop, and
+  // %block_k is the corresponding return value.
+  // If the function has void type, there will be no OpPhi instruction and the
+  // last instruction will be OpReturn.
+
+  // The id of the function to which the transformation is being applied.
+  uint32 function_id = 1;
+
+  // A fresh id for the header of the new outer loop.
+  uint32 outer_header_id = 2;
+
+  // A fresh id for the new return block of the function,
+  // i.e. the merge block of the new outer loop.
+  uint32 outer_return_id = 3;
+
+  // A fresh id for the value that will be returned.
+  // This is ignored if the function has void return type.
+  uint32 return_val_id = 4;
+
+  // An existing id of the same type as the return value, which is
+  // available to use at the end of the entry block.
+  // This is ignored if the function has void return type or if no
+  // loops in the function contain a return instruction.
+  // If the function is not void, the transformation will add an
+  // OpPhi instruction to each merge block whose associated loop
+  // contains at least a return instruction. The value associated
+  // with existing predecessors from which the function cannot be
+  // returning will be this id, used as a placeholder.
+  uint32 any_returnable_val_id = 5;
+
+  // The information needed to modify the merge blocks of
+  // loops containing return instructions.
+  repeated ReturnMergingInfo return_merging_info = 6;
+}
+
 message TransformationMoveBlockDown {
 
   // A transformation that moves a basic block to be one position lower in
diff --git a/source/fuzz/transformation_flatten_conditional_branch.h b/source/fuzz/transformation_flatten_conditional_branch.h
index 2edbb46..fd8c1f5 100644
--- a/source/fuzz/transformation_flatten_conditional_branch.h
+++ b/source/fuzz/transformation_flatten_conditional_branch.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H
-#define SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H
+#ifndef SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
+#define SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
 
 #include "source/fuzz/transformation.h"
 
@@ -119,4 +119,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H
+#endif  // SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
diff --git a/source/fuzz/transformation_merge_function_returns.cpp b/source/fuzz/transformation_merge_function_returns.cpp
new file mode 100644
index 0000000..390adb3
--- /dev/null
+++ b/source/fuzz/transformation_merge_function_returns.cpp
@@ -0,0 +1,816 @@
+// Copyright (c) 2020 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_merge_function_returns.h"
+
+#include "source/fuzz/comparator_deep_blocks_first.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
+    const protobufs::TransformationMergeFunctionReturns& message)
+    : message_(message) {}
+
+TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
+    uint32_t function_id, uint32_t outer_header_id, uint32_t outer_return_id,
+    uint32_t return_val_id, uint32_t any_returnable_val_id,
+    const std::vector<protobufs::ReturnMergingInfo>& returns_merging_info) {
+  message_.set_function_id(function_id);
+  message_.set_outer_header_id(outer_header_id);
+  message_.set_outer_return_id(outer_return_id);
+  message_.set_return_val_id(return_val_id);
+  message_.set_any_returnable_val_id(any_returnable_val_id);
+  for (const auto& return_merging_info : returns_merging_info) {
+    *message_.add_return_merging_info() = return_merging_info;
+  }
+}
+
+bool TransformationMergeFunctionReturns::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  auto function = ir_context->GetFunction(message_.function_id());
+  // The function must exist.
+  if (!function) {
+    return false;
+  }
+
+  // The entry block must end in an unconditional branch.
+  if (function->entry()->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  // The module must contain an OpConstantTrue instruction.
+  if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+                                        true, false)) {
+    return false;
+  }
+
+  // The module must contain an OpConstantFalse instruction.
+  if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+                                        false, false)) {
+    return false;
+  }
+
+  // Check that the fresh ids provided are fresh and distinct.
+  std::set<uint32_t> used_fresh_ids;
+  for (uint32_t id : {message_.outer_header_id(), message_.outer_return_id()}) {
+    if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+                                                             &used_fresh_ids)) {
+      return false;
+    }
+  }
+
+  // Check the additional fresh id required if the function is not void.
+  auto function_type = ir_context->get_type_mgr()->GetType(function->type_id());
+  assert(function_type && "The function type should always exist.");
+
+  if (!function_type->AsVoid() &&
+      (!message_.return_val_id() ||
+       !CheckIdIsFreshAndNotUsedByThisTransformation(
+           message_.return_val_id(), ir_context, &used_fresh_ids))) {
+    return false;
+  }
+
+  // Get a map from the types for which ids are available at the end of the
+  // entry block to one of the ids with that type. We compute this here to avoid
+  // potentially doing it multiple times later on.
+  auto types_to_available_ids =
+      GetTypesToIdAvailableAfterEntryBlock(ir_context);
+
+  // Get the reachable return blocks.
+  auto return_blocks =
+      fuzzerutil::GetReachableReturnBlocks(ir_context, message_.function_id());
+
+  // Map each merge block of loops containing reachable return blocks to the
+  // corresponding returning predecessors (all the blocks that, at the end of
+  // the transformation, will branch to the merge block because the function is
+  // returning).
+  std::map<uint32_t, std::set<uint32_t>> merge_blocks_to_returning_preds;
+  for (uint32_t block : return_blocks) {
+    uint32_t merge_block =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(block);
+
+    while (merge_block != 0) {
+      // If we have seen this merge block before, update the corresponding set
+      // and break out of the loop.
+      if (merge_blocks_to_returning_preds.count(merge_block)) {
+        merge_blocks_to_returning_preds[merge_block].emplace(block);
+        break;
+      }
+
+      // If we have not seen this merge block before, add a new entry and walk
+      // up the loop tree.
+      merge_blocks_to_returning_preds.emplace(merge_block,
+                                              std::set<uint32_t>({block}));
+
+      // Walk up the loop tree.
+      block = merge_block;
+      merge_block =
+          ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(merge_block);
+    }
+  }
+
+  // Instructions in the relevant merge blocks must be restricted to OpLabel,
+  // OpPhi and OpBranch.
+  for (const auto& merge_block_entry : merge_blocks_to_returning_preds) {
+    uint32_t merge_block = merge_block_entry.first;
+    bool all_instructions_allowed =
+        ir_context->get_instr_block(merge_block)
+            ->WhileEachInst([](opt::Instruction* inst) {
+              return inst->opcode() == SpvOpLabel ||
+                     inst->opcode() == SpvOpPhi ||
+                     inst->opcode() == SpvOpBranch;
+            });
+    if (!all_instructions_allowed) {
+      return false;
+    }
+  }
+
+  auto merge_blocks_to_info = GetMappingOfMergeBlocksToInfo();
+
+  // For each relevant merge block, check that the correct ids are available.
+  for (const auto& merge_block_entry : merge_blocks_to_returning_preds) {
+    if (!CheckThatTheCorrectIdsAreGivenForMergeBlock(
+            merge_block_entry.first, merge_blocks_to_info,
+            types_to_available_ids, function_type->AsVoid(), ir_context,
+            transformation_context, &used_fresh_ids)) {
+      return false;
+    }
+  }
+
+  // If the function has a non-void return type, and there are merge loops which
+  // contain return instructions, we need to check that either:
+  // - |message_.any_returnable_val_id| exists. In this case, it must have the
+  //   same type as the return type of the function and be available at the end
+  //   of the entry block.
+  // - a suitable id, available at the end of the entry block can be found in
+  //   the module.
+  if (!function_type->AsVoid() && !merge_blocks_to_returning_preds.empty()) {
+    auto returnable_val_def =
+        ir_context->get_def_use_mgr()->GetDef(message_.any_returnable_val_id());
+    if (!returnable_val_def) {
+      // Check if a suitable id can be found in the module.
+      if (types_to_available_ids.count(function->type_id()) == 0) {
+        return false;
+      }
+    } else if (returnable_val_def->type_id() != function->type_id()) {
+      return false;
+    } else if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+                   ir_context, function->entry()->terminator(),
+                   message_.any_returnable_val_id())) {
+      // The id must be available at the end of the entry block.
+      return false;
+    }
+  }
+
+  // Check that adding new predecessors to the relevant merge blocks does not
+  // render any instructions invalid (each id definition must still dominate
+  // each of its uses).
+  if (!CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
+          ir_context, function, merge_blocks_to_returning_preds)) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationMergeFunctionReturns::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto function = ir_context->GetFunction(message_.function_id());
+  auto function_type = ir_context->get_type_mgr()->GetType(function->type_id());
+
+  // Get a map from the types for which ids are available at the end of the
+  // entry block to one of the ids with that type. We compute this here to avoid
+  // potentially doing it multiple times later on.
+  auto types_to_available_ids =
+      GetTypesToIdAvailableAfterEntryBlock(ir_context);
+
+  uint32_t bool_type = fuzzerutil::MaybeGetBoolType(ir_context);
+
+  uint32_t constant_true = fuzzerutil::MaybeGetBoolConstant(
+      ir_context, *transformation_context, true, false);
+
+  uint32_t constant_false = fuzzerutil::MaybeGetBoolConstant(
+      ir_context, *transformation_context, false, false);
+
+  // Get the reachable return blocks.
+  auto return_blocks =
+      fuzzerutil::GetReachableReturnBlocks(ir_context, message_.function_id());
+
+  // Keep a map from the relevant merge blocks to a mapping from each of the
+  // returning predecessors to the corresponding pair (return value,
+  // boolean specifying whether the function is returning). Returning
+  // predecessors are blocks in the loop (not further nested inside loops),
+  // which either return or are merge blocks of nested loops containing return
+  // instructions.
+  std::map<uint32_t, std::map<uint32_t, std::pair<uint32_t, uint32_t>>>
+      merge_blocks_to_returning_predecessors;
+
+  // Initialise the map, mapping each relevant merge block to an empty map.
+  for (uint32_t ret_block_id : return_blocks) {
+    uint32_t merge_block_id =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(ret_block_id);
+
+    while (merge_block_id != 0 &&
+           !merge_blocks_to_returning_predecessors.count(merge_block_id)) {
+      merge_blocks_to_returning_predecessors.emplace(
+          merge_block_id, std::map<uint32_t, std::pair<uint32_t, uint32_t>>());
+      merge_block_id = ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(
+          merge_block_id);
+    }
+  }
+
+  // Get a reference to an instruction with the same type id as the function's
+  // return type, if the type of the function is not void and ther are loops
+  // containing return instructions.
+  uint32_t returnable_val_id = 0;
+  if (!function_type->AsVoid() &&
+      !merge_blocks_to_returning_predecessors.empty()) {
+    // If |message.any_returnable_val_id| can be found in the module, use it.
+    // Otherwise, use another suitable id found in the module.
+    auto returnable_val_def =
+        ir_context->get_def_use_mgr()->GetDef(message_.any_returnable_val_id());
+    returnable_val_id = returnable_val_def
+                            ? returnable_val_def->result_id()
+                            : types_to_available_ids[function->type_id()];
+  }
+
+  // Keep a map from all the new predecessors of the merge block of the new
+  // outer loop, to the related return value ids.
+  std::map<uint32_t, uint32_t> outer_merge_predecessors;
+
+  // Adjust the return blocks and add the related information to the map or
+  // |outer_merge_predecessors| set.
+  for (uint32_t ret_block_id : return_blocks) {
+    auto ret_block = ir_context->get_instr_block(ret_block_id);
+
+    // Get the return value id (if the function is not void).
+    uint32_t ret_val_id =
+        function_type->AsVoid()
+            ? 0
+            : ret_block->terminator()->GetSingleWordInOperand(0);
+
+    uint32_t merge_block_id =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(ret_block_id);
+
+    // Add a new entry to the map corresponding to the merge block of the
+    // innermost enclosing loop (or that of the new outer loop if there is no
+    // enclosing loop).
+    if (merge_block_id != 0) {
+      merge_blocks_to_returning_predecessors[merge_block_id].emplace(
+          ret_block_id,
+          std::pair<uint32_t, uint32_t>(ret_val_id, constant_true));
+    } else {
+      // If there is no enclosing loop, the block will branch to the merge block
+      // of the new outer loop.
+      merge_block_id = message_.outer_return_id();
+      outer_merge_predecessors.emplace(ret_block_id, ret_val_id);
+    }
+
+    // Replace the return instruction with an unconditional branch.
+    ret_block->terminator()->SetOpcode(SpvOpBranch);
+    ret_block->terminator()->SetInOperands(
+        {{SPV_OPERAND_TYPE_RESULT_ID, {merge_block_id}}});
+  }
+
+  // Get a list of all the relevant merge blocks.
+  std::vector<uint32_t> merge_blocks;
+  merge_blocks.reserve(merge_blocks_to_returning_predecessors.size());
+  for (const auto& entry : merge_blocks_to_returning_predecessors) {
+    merge_blocks.emplace_back(entry.first);
+  }
+
+  // Sort the list so that deeper merge blocks come first.
+  // We need to consider deeper merge blocks first so that, when a merge block
+  // is considered, all the merge blocks enclosed by the corresponding loop have
+  // already been considered and, thus, the mapping from this merge block to the
+  // returning predecessors is complete.
+  std::sort(merge_blocks.begin(), merge_blocks.end(),
+            ComparatorDeepBlocksFirst(ir_context));
+
+  auto merge_blocks_to_info = GetMappingOfMergeBlocksToInfo();
+
+  // Adjust the merge blocks and add the related information to the map or
+  // |outer_merge_predecessors| set.
+  for (uint32_t merge_block_id : merge_blocks) {
+    // Get the info corresponding to |merge_block| from the map, if a
+    // corresponding entry exists. Otherwise use overflow ids and find suitable
+    // ids in the module.
+    protobufs::ReturnMergingInfo* info =
+        merge_blocks_to_info.count(merge_block_id)
+            ? &merge_blocks_to_info[merge_block_id]
+            : nullptr;
+
+    uint32_t is_returning_id =
+        info ? info->is_returning_id()
+             : transformation_context->GetOverflowIdSource()
+                   ->GetNextOverflowId();
+
+    uint32_t maybe_return_val_id = 0;
+    if (!function_type->AsVoid()) {
+      maybe_return_val_id = info ? info->maybe_return_val_id()
+                                 : transformation_context->GetOverflowIdSource()
+                                       ->GetNextOverflowId();
+    }
+
+    // Map from existing OpPhi to overflow ids. If there is no mapping, get an
+    // empty map.
+    auto phi_to_id = info ? fuzzerutil::RepeatedUInt32PairToMap(
+                                *merge_blocks_to_info[merge_block_id]
+                                     .mutable_opphi_to_suitable_id())
+                          : std::map<uint32_t, uint32_t>();
+
+    // Get a reference to the info related to the returning predecessors.
+    const auto& returning_preds =
+        merge_blocks_to_returning_predecessors[merge_block_id];
+
+    // Get a set of the original predecessors.
+    auto preds_list = ir_context->cfg()->preds(merge_block_id);
+    auto preds = std::set<uint32_t>(preds_list.begin(), preds_list.end());
+
+    auto merge_block = ir_context->get_instr_block(merge_block_id);
+
+    // Adjust the existing OpPhi instructions.
+    merge_block->ForEachPhiInst([&preds, &returning_preds, &phi_to_id,
+                                 &types_to_available_ids](
+                                    opt::Instruction* inst) {
+      // We need a placeholder value id. If |phi_to_id| contains a mapping
+      // for this instruction, we use the given id, otherwise a suitable id
+      // for the instruction's type from |types_to_available_ids|.
+      uint32_t placeholder_val_id =
+          phi_to_id.count(inst->result_id())
+              ? phi_to_id[inst->result_id()]
+              : types_to_available_ids[inst->type_id()];
+      assert(placeholder_val_id &&
+             "We should always be able to find a suitable if the "
+             "transformation is applicable.");
+
+      // Add a pair of operands (placeholder id, new predecessor) for each
+      // new predecessor of the merge block.
+      for (const auto& entry : returning_preds) {
+        // A returning predecessor may already be a predecessor of the
+        // block. In that case, we should not add new operands.
+        // Each entry is in the form (predecessor, {return val, is returning}).
+        if (!preds.count(entry.first)) {
+          inst->AddOperand({SPV_OPERAND_TYPE_RESULT_ID, {placeholder_val_id}});
+          inst->AddOperand({SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
+        }
+      }
+    });
+
+    // If the function is not void, add a new OpPhi instructions to collect the
+    // return value from the returning predecessors.
+    if (!function_type->AsVoid()) {
+      opt::Instruction::OperandList operand_list;
+
+      // Add two operands (return value, predecessor) for each returning
+      // predecessor.
+      for (auto entry : returning_preds) {
+        // Each entry is in the form (predecessor, {return value,
+        // is returning}).
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.second.first}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
+      }
+
+      // Add two operands for each original predecessor from which the function
+      // does not return.
+      for (uint32_t original_pred : preds) {
+        // Only add operands if the function cannot be returning from this
+        // block.
+        if (returning_preds.count(original_pred)) {
+          continue;
+        }
+
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {returnable_val_id}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {original_pred}});
+      }
+
+      // Insert the instruction.
+      merge_block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpPhi, function->type_id(), maybe_return_val_id,
+          std::move(operand_list)));
+
+      fuzzerutil::UpdateModuleIdBound(ir_context, maybe_return_val_id);
+    }
+
+    // Add an OpPhi instruction deciding whether the function is returning.
+    {
+      opt::Instruction::OperandList operand_list;
+
+      // Add two operands (return value, is returning) for each returning
+      // predecessor.
+      for (auto entry : returning_preds) {
+        // Each entry is in the form (predecessor, {return value,
+        // is returning}).
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.second.second}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
+      }
+
+      // Add two operands for each original predecessor from which the function
+      // does not return.
+      for (uint32_t original_pred : preds) {
+        // Only add operands if the function cannot be returning from this
+        // block.
+        if (returning_preds.count(original_pred)) {
+          continue;
+        }
+
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {constant_false}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {original_pred}});
+      }
+
+      // Insert the instruction.
+      merge_block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpPhi, bool_type, is_returning_id,
+          std::move(operand_list)));
+
+      fuzzerutil::UpdateModuleIdBound(ir_context, is_returning_id);
+    }
+
+    // Change the branching instruction of the block.
+    assert(merge_block->terminator()->opcode() == SpvOpBranch &&
+           "Each block should branch unconditionally to the next.");
+
+    // Add a new entry to the map corresponding to the merge block of the
+    // innermost enclosing loop (or that of the new outer loop if there is no
+    // enclosing loop).
+    uint32_t enclosing_merge =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(merge_block_id);
+    if (enclosing_merge == 0) {
+      enclosing_merge = message_.outer_return_id();
+      outer_merge_predecessors.emplace(merge_block_id, maybe_return_val_id);
+    } else {
+      merge_blocks_to_returning_predecessors[enclosing_merge].emplace(
+          merge_block_id,
+          std::pair<uint32_t, uint32_t>(maybe_return_val_id, is_returning_id));
+    }
+
+    // Get the current successor.
+    uint32_t original_succ =
+        merge_block->terminator()->GetSingleWordInOperand(0);
+    // Leave the instruction as it is if the block already branches to the merge
+    // block of the enclosing loop.
+    if (original_succ == enclosing_merge) {
+      continue;
+    }
+
+    // The block should branch to |enclosing_merge| if |is_returning_id| is
+    // true, to |original_succ| otherwise.
+    merge_block->terminator()->SetOpcode(SpvOpBranchConditional);
+    merge_block->terminator()->SetInOperands(
+        {{SPV_OPERAND_TYPE_RESULT_ID, {is_returning_id}},
+         {SPV_OPERAND_TYPE_RESULT_ID, {enclosing_merge}},
+         {SPV_OPERAND_TYPE_RESULT_ID, {original_succ}}});
+  }
+
+  assert(function->entry()->terminator()->opcode() == SpvOpBranch &&
+         "The entry block should branch unconditionally to another block.");
+  uint32_t block_after_entry =
+      function->entry()->terminator()->GetSingleWordInOperand(0);
+
+  // Create the header for the new outer loop.
+  auto outer_loop_header =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.outer_header_id(),
+          opt::Instruction::OperandList()));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.outer_header_id());
+
+  // Add the instruction: OpLoopMerge %outer_return_id %outer_header_id None
+  // The header is the continue block of the outer loop.
+  outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpLoopMerge, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_RESULT_ID, {message_.outer_return_id()}},
+          {SPV_OPERAND_TYPE_RESULT_ID, {message_.outer_header_id()}},
+          {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}));
+
+  // Add conditional branch:
+  // 	OpBranchConditional %true %block_after_entry %outer_header_id
+  // This will always branch to %block_after_entry, but it also creates a back
+  // edge for the loop (which is never traversed).
+  outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranchConditional, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_RESULT_ID, {constant_true}},
+          {SPV_OPERAND_TYPE_RESULT_ID, {block_after_entry}},
+          {SPV_OPERAND_TYPE_LOOP_CONTROL, {message_.outer_header_id()}}}));
+
+  // Insert the header right after the entry block.
+  function->InsertBasicBlockAfter(std::move(outer_loop_header),
+                                  function->entry().get());
+
+  // Update the branching instruction of the entry block.
+  function->entry()->terminator()->SetInOperands(
+      {{SPV_OPERAND_TYPE_RESULT_ID, {message_.outer_header_id()}}});
+
+  // Create the merge block for the loop (and return block for the function).
+  auto outer_return_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.outer_return_id(),
+          opt::Instruction::OperandList()));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.outer_return_id());
+
+  // If the function is not void, insert an instruction to collect the return
+  // value from the predecessors and an OpReturnValue instruction.
+  if (!function_type->AsVoid()) {
+    opt::Instruction::OperandList operand_list;
+
+    // Add two operands (return value, predecessor) for each predecessor.
+    for (auto entry : outer_merge_predecessors) {
+      // Each entry is in the form (predecessor, return value).
+      operand_list.emplace_back(
+          opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.second}});
+      operand_list.emplace_back(
+          opt::Operand{SPV_OPERAND_TYPE_RESULT_ID, {entry.first}});
+    }
+
+    // Insert the OpPhi instruction.
+    outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpPhi, function->type_id(), message_.return_val_id(),
+        std::move(operand_list)));
+
+    fuzzerutil::UpdateModuleIdBound(ir_context, message_.return_val_id());
+
+    // Insert the OpReturnValue instruction.
+    outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpReturnValue, 0, 0,
+        opt::Instruction::OperandList{
+            {SPV_OPERAND_TYPE_RESULT_ID, {message_.return_val_id()}}}));
+  } else {
+    // Insert an OpReturn instruction (the function is void).
+    outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpReturn, 0, 0, opt::Instruction::OperandList{}));
+  }
+
+  // Insert the new return block at the end of the function.
+  function->AddBasicBlock(std::move(outer_return_block));
+
+  // All analyses must be invalidated because the structure of the module was
+  // changed.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+std::unordered_set<uint32_t> TransformationMergeFunctionReturns::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result;
+  result.emplace(message_.outer_header_id());
+  result.emplace(message_.outer_return_id());
+  // |message_.return_val_info| can be 0 if the function is void.
+  if (message_.return_val_id()) {
+    result.emplace(message_.return_val_id());
+  }
+
+  for (auto merging_info : message_.return_merging_info()) {
+    result.emplace(merging_info.is_returning_id());
+    // |maybe_return_val_id| can be 0 if the function is void.
+    if (merging_info.maybe_return_val_id()) {
+      result.emplace(merging_info.maybe_return_val_id());
+    }
+  }
+
+  return result;
+}
+
+protobufs::Transformation TransformationMergeFunctionReturns::ToMessage()
+    const {
+  return protobufs::Transformation();
+}
+
+std::map<uint32_t, protobufs::ReturnMergingInfo>
+TransformationMergeFunctionReturns::GetMappingOfMergeBlocksToInfo() const {
+  std::map<uint32_t, protobufs::ReturnMergingInfo> result;
+  for (const auto& info : message_.return_merging_info()) {
+    result.emplace(info.merge_block_id(), info);
+  }
+  return result;
+}
+
+std::map<uint32_t, uint32_t>
+TransformationMergeFunctionReturns::GetTypesToIdAvailableAfterEntryBlock(
+    opt::IRContext* ir_context) const {
+  std::map<uint32_t, uint32_t> result;
+  // Consider all global declarations
+  for (auto& global : ir_context->module()->types_values()) {
+    if (global.HasResultId() && global.type_id()) {
+      result.emplace(global.type_id(), global.result_id());
+    }
+  }
+
+  auto function = ir_context->GetFunction(message_.function_id());
+  assert(function && "The function must exist.");
+
+  // Consider all function parameters
+  function->ForEachParam([&result](opt::Instruction* param) {
+    if (param->HasResultId() && param->type_id()) {
+      result.emplace(param->type_id(), param->result_id());
+    }
+  });
+
+  // Consider all the instructions in the entry block.
+  for (auto& inst : *function->entry()) {
+    if (inst.HasResultId() && inst.type_id()) {
+      result.emplace(inst.type_id(), inst.result_id());
+    }
+  }
+
+  return result;
+}
+
+bool TransformationMergeFunctionReturns::
+    CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
+        opt::IRContext* ir_context, const opt::Function* function,
+        const std::map<uint32_t, std::set<uint32_t>>&
+            merge_blocks_to_new_predecessors) {
+  for (const auto& merge_block_entry : merge_blocks_to_new_predecessors) {
+    uint32_t merge_block = merge_block_entry.first;
+    const auto& returning_preds = merge_block_entry.second;
+
+    // Find a list of blocks in which there might be problematic definitions.
+    // These are all the blocks that dominate the merge block but do not
+    // dominate all of the new predecessors.
+    std::vector<opt::BasicBlock*> problematic_blocks;
+
+    auto dominator_analysis = ir_context->GetDominatorAnalysis(function);
+
+    // Start from the immediate dominator of the merge block.
+    auto current_block = dominator_analysis->ImmediateDominator(merge_block);
+    assert(current_block &&
+           "Each merge block should have at least one dominator.");
+
+    for (uint32_t pred : returning_preds) {
+      while (!dominator_analysis->Dominates(current_block->id(), pred)) {
+        // The current block does not dominate all of the new predecessor
+        // blocks, so it might be problematic.
+        problematic_blocks.emplace_back(current_block);
+
+        // Walk up the dominator tree.
+        current_block = dominator_analysis->ImmediateDominator(current_block);
+        assert(current_block &&
+               "We should be able to find a dominator for all the blocks, "
+               "since they must all be dominated at least by the header.");
+      }
+    }
+
+    // Identify the loop header corresponding to the merge block.
+    uint32_t loop_header =
+        fuzzerutil::GetLoopFromMergeBlock(ir_context, merge_block);
+
+    // For all the ids defined in blocks inside |problematic_blocks|, check that
+    // all their uses are either:
+    // - inside the loop (or in the loop header). If this is the case, the path
+    //   from the definition to the use does not go through the merge block, so
+    //   adding new predecessor to it is not a problem.
+    // - inside an OpPhi instruction in the merge block. If this is the case,
+    //   the definition does not need to dominate the merge block.
+    for (auto block : problematic_blocks) {
+      assert((block->id() == loop_header ||
+              ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
+                  block->id()) == loop_header) &&
+             "The problematic blocks should all be inside the loop (also "
+             "considering the header).");
+      bool dominance_rules_maintained =
+          block->WhileEachInst([ir_context, loop_header,
+                                merge_block](opt::Instruction* instruction) {
+            // Instruction without a result id do not cause any problems.
+            if (!instruction->HasResultId()) {
+              return true;
+            }
+
+            // Check that all the uses of the id are inside the loop.
+            return ir_context->get_def_use_mgr()->WhileEachUse(
+                instruction->result_id(),
+                [ir_context, loop_header, merge_block](
+                    opt::Instruction* inst_use, uint32_t /* unused */) {
+                  uint32_t block_use =
+                      ir_context->get_instr_block(inst_use)->id();
+
+                  // The usage is OK if it is inside the loop (including the
+                  // header).
+                  if (block_use == loop_header ||
+                      ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
+                          block_use)) {
+                    return true;
+                  }
+
+                  // The usage is OK if it is inside an OpPhi instruction in the
+                  // merge block.
+                  return block_use == merge_block &&
+                         inst_use->opcode() == SpvOpPhi;
+                });
+          });
+
+      // If not all instructions in the block satisfy the requirement, the
+      // transformation is not applicable.
+      if (!dominance_rules_maintained) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool TransformationMergeFunctionReturns::
+    CheckThatTheCorrectIdsAreGivenForMergeBlock(
+        uint32_t merge_block,
+        const std::map<uint32_t, protobufs::ReturnMergingInfo>&
+            merge_blocks_to_info,
+        const std::map<uint32_t, uint32_t>& types_to_available_id,
+        bool function_is_void, opt::IRContext* ir_context,
+        const TransformationContext& transformation_context,
+        std::set<uint32_t>* used_fresh_ids) {
+  // A map from OpPhi ids to ids of the same type available at the beginning
+  // of the merge block.
+  std::map<uint32_t, uint32_t> phi_to_id;
+
+  if (merge_blocks_to_info.count(merge_block) > 0) {
+    // If the map contains an entry for the merge block, check that the fresh
+    // ids are fresh and distinct.
+    auto info = merge_blocks_to_info.at(merge_block);
+    if (!info.is_returning_id() ||
+        !CheckIdIsFreshAndNotUsedByThisTransformation(
+            info.is_returning_id(), ir_context, used_fresh_ids)) {
+      return false;
+    }
+
+    if (!function_is_void &&
+        (!info.maybe_return_val_id() ||
+         !CheckIdIsFreshAndNotUsedByThisTransformation(
+             info.maybe_return_val_id(), ir_context, used_fresh_ids))) {
+      return false;
+    }
+
+    // Get the mapping from OpPhis to suitable ids.
+    phi_to_id = fuzzerutil::RepeatedUInt32PairToMap(
+        *info.mutable_opphi_to_suitable_id());
+  } else {
+    // If the map does not contain an entry for the merge block, check that
+    // overflow ids are available.
+    if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+      return false;
+    }
+  }
+
+  // For each OpPhi instruction, check that a suitable placeholder id is
+  // available.
+  bool suitable_info_for_phi =
+      ir_context->get_instr_block(merge_block)
+          ->WhileEachPhiInst([ir_context, &phi_to_id,
+                              &types_to_available_id](opt::Instruction* inst) {
+            if (phi_to_id.count(inst->result_id()) > 0) {
+              // If there exists a mapping for this instruction and the
+              // placeholder id exists in the module, check that it has the
+              // correct type and it is available before the instruction.
+              auto placeholder_def = ir_context->get_def_use_mgr()->GetDef(
+                  phi_to_id[inst->result_id()]);
+              if (placeholder_def) {
+                if (inst->type_id() != placeholder_def->type_id()) {
+                  return false;
+                }
+                if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+                        ir_context, inst, placeholder_def->result_id())) {
+                  return false;
+                }
+
+                return true;
+              }
+            }
+
+            // If there is no mapping, check if there is a suitable id
+            // available at the end of the entry block.
+            return types_to_available_id.count(inst->type_id()) > 0;
+          });
+
+  if (!suitable_info_for_phi) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_merge_function_returns.h b/source/fuzz/transformation_merge_function_returns.h
new file mode 100644
index 0000000..4b29936
--- /dev/null
+++ b/source/fuzz/transformation_merge_function_returns.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2020 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_MERGE_FUNCTION_RETURNS_
+#define SOURCE_FUZZ_TRANSFORMATION_MERGE_FUNCTION_RETURNS_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationMergeFunctionReturns : public Transformation {
+ public:
+  explicit TransformationMergeFunctionReturns(
+      const protobufs::TransformationMergeFunctionReturns& message);
+
+  TransformationMergeFunctionReturns(
+      uint32_t function_id, uint32_t outer_header_id, uint32_t outer_return_id,
+      uint32_t return_val_id, uint32_t any_returnable_val_id,
+      const std::vector<protobufs::ReturnMergingInfo>& returns_merging_info);
+
+  // - |message_.function_id| is the id of a function.
+  // - The entry block of |message_.function_id| branches unconditionally to
+  //   another block.
+  // - |message_.any_returnable_val_id| is an id whose type is the same as the
+  //   return type of the function and which is available at the end of the
+  //   entry block. If this id is not found in the module, the transformation
+  //   will try to find a suitable one.
+  //   If the function is void, or no loops in the function contain return
+  //   statements, this id will be ignored.
+  // - Merge blocks of reachable loops that contain return statements only
+  //   consist of OpLabel, OpPhi or OpBranch instructions.
+  // - The model contains OpConstantTrue and OpConstantFalse instructions.
+  // - For all merge blocks of reachable loops that contain return statements,
+  //   either:
+  //   - a mapping is provided in |message_.return_merging_info|, all of the
+  //     corresponding fresh ids are valid and, for each OpPhi instruction in
+  //     the block, there is a mapping to an available id of the same type in
+  //     |opphi_to_suitable_id| or a suitable id, available at the end of the
+  //     entry block, can be found in the module.
+  //   - there is no mapping, but overflow ids are available and, for every
+  //     OpPhi instruction in the merge blocks that need to be modified, a
+  //     suitable id, available at the end of the entry block, can be found.
+  // - The addition of new predecessors to the relevant merge blocks does not
+  //   cause any id use to be invalid (i.e. every id must dominate all its uses
+  //   even after the transformation has added new branches).
+  // - All of the fresh ids that are provided and needed by the transformation
+  //   are valid.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Changes the function so that there is only one reachable return
+  // instruction. The function is enclosed by an outer loop, whose merge block
+  // is the new return block. All existing return statements are replaced by
+  // branch instructions to the merge block of the loop enclosing them, and
+  // OpPhi instructions are used to keep track of the return value and of
+  // whether the function is returning.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  // Returns a map from merge block ids to the corresponding info in
+  // |message_.return_merging_info|.
+  std::map<uint32_t, protobufs::ReturnMergingInfo>
+  GetMappingOfMergeBlocksToInfo() const;
+
+  // Returns a map from type ids to an id with that type and which is available
+  // at the end of the entry block of |message_.function_id|.
+  // Assumes that the function exists.
+  std::map<uint32_t, uint32_t> GetTypesToIdAvailableAfterEntryBlock(
+      opt::IRContext* ir_context) const;
+
+  // Returns true if adding new predecessors to the given loop merge blocks
+  // does not render any instructions invalid (each id definition must still
+  // dominate all of its uses). The loop merge blocks and corresponding new
+  // predecessors to consider are given in |merge_blocks_to_new_predecessors|.
+  // All of the new predecessors are assumed to be inside the loop associated
+  // with the corresponding loop merge block.
+  static bool CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
+      opt::IRContext* ir_context, const opt::Function* function,
+      const std::map<uint32_t, std::set<uint32_t>>&
+          merge_blocks_to_new_predecessors);
+
+  // Returns true if the required ids for |merge_block| are provided in the
+  // |merge_blocks_to_info| map, or if ids of the suitable type can be found.
+  static bool CheckThatTheCorrectIdsAreGivenForMergeBlock(
+      uint32_t merge_block,
+      const std::map<uint32_t, protobufs::ReturnMergingInfo>&
+          merge_blocks_to_info,
+      const std::map<uint32_t, uint32_t>& types_to_available_id,
+      bool function_is_void, opt::IRContext* ir_context,
+      const TransformationContext& transformation_context,
+      std::set<uint32_t>* used_fresh_ids);
+
+  protobufs::TransformationMergeFunctionReturns message_;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MERGE_FUNCTION_RETURNS_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 924addb..e287d8d 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -75,6 +75,7 @@
           transformation_load_test.cpp
           transformation_make_vector_operation_dynamic_test.cpp
           transformation_merge_blocks_test.cpp
+          transformation_merge_function_returns_test.cpp
           transformation_move_block_down_test.cpp
           transformation_move_instruction_down_test.cpp
           transformation_mutate_pointer_test.cpp
diff --git a/test/fuzz/transformation_merge_function_returns_test.cpp b/test/fuzz/transformation_merge_function_returns_test.cpp
new file mode 100644
index 0000000..2c8d949
--- /dev/null
+++ b/test/fuzz/transformation_merge_function_returns_test.cpp
@@ -0,0 +1,1784 @@
+// Copyright (c) 2020 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_merge_function_returns.h"
+
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+protobufs::ReturnMergingInfo MakeReturnMergingInfo(
+    uint32_t merge_block_id, uint32_t is_returning_id,
+    uint32_t maybe_return_val_id,
+    std::map<uint32_t, uint32_t> opphi_to_suitable_id) {
+  protobufs::ReturnMergingInfo result;
+  result.set_merge_block_id(merge_block_id);
+  result.set_is_returning_id(is_returning_id);
+  result.set_maybe_return_val_id(maybe_return_val_id);
+  *result.mutable_opphi_to_suitable_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(opphi_to_suitable_id);
+  return result;
+}
+
+TEST(TransformationMergeFunctionReturnsTest, SimpleInapplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypeFunction %7
+          %9 = OpTypeBool
+         %10 = OpConstantTrue %9
+         %11 = OpConstantFalse %9
+         %12 = OpConstant %5 0
+         %13 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+         %15 = OpFunctionCall %3 %16
+         %17 = OpFunctionCall %3 %18
+         %19 = OpFunctionCall %3 %20
+         %21 = OpFunctionCall %7 %22
+               OpReturn
+               OpFunctionEnd
+         %16 = OpFunction %3 None %4
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %10 %25 %26
+         %25 = OpLabel
+               OpReturn
+         %26 = OpLabel
+               OpReturn
+         %24 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %29 %30 None
+               OpBranch %31
+         %31 = OpLabel
+               OpBranchConditional %10 %32 %29
+         %32 = OpLabel
+               OpReturn
+         %30 = OpLabel
+               OpBranch %28
+         %29 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %3 None %4
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+               OpBranchConditional %10 %38 %35
+         %38 = OpLabel
+               OpReturn
+         %36 = OpLabel
+               OpBranch %34
+         %35 = OpLabel
+         %39 = OpFunctionCall %3 %18
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %7 None %8
+         %40 = OpLabel
+               OpBranch %51
+         %51 = OpLabel
+               OpLoopMerge %41 %53 None
+               OpBranchConditional %10 %42 %41
+         %42 = OpLabel
+         %43 = OpConvertSToF %7 %12
+               OpReturnValue %43
+         %41 = OpLabel
+         %44 = OpConvertSToF %7 %13
+               OpReturnValue %44
+         %53 = OpLabel
+               OpBranch %51
+               OpFunctionEnd
+         %45 = OpFunction %5 None %6
+         %46 = OpLabel
+               OpBranch %52
+         %52 = OpLabel
+         %47 = OpConvertSToF %7 %13
+               OpLoopMerge %48 %54 None
+               OpBranchConditional %10 %49 %48
+         %49 = OpLabel
+               OpReturnValue %12
+         %48 = OpLabel
+         %50 = OpCopyObject %5 %12
+               OpReturnValue %13
+         %54 = OpLabel
+               OpBranch %52
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Function %1 does not exist.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(1, 100, 101, 0, 0, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The entry block (%22) of function %15 does not branch unconditionally to
+  // the following block.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(16, 100, 101, 0, 0, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %28 is the merge block of a loop containing a return instruction, but
+  // it contains an OpReturn instruction (so, it contains instructions that are
+  // not OpLabel, OpPhi or OpBranch).
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          18, 100, 101, 0, 0, {{MakeReturnMergingInfo(29, 102, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Block %34 is the merge block of a loop containing a return instruction, but
+  // it contains an OpFunctionCall instruction (so, it contains instructions
+  // that are not OpLabel, OpPhi or OpBranch).
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          20, 100, 101, 0, 0, {{MakeReturnMergingInfo(35, 102, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Id %1000 cannot be found in the module and there is no id of the correct
+  // type (float) available at the end of the entry block of function %21.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(22, 100, 101, 102, 1000, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %47 is of type float, while function %45 has return type int.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(45, 100, 101, 102, 47, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %50 is not available at the end of the entry block of function %45.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(45, 100, 101, 102, 50, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, MissingBooleans) {
+  {
+    // OpConstantTrue is missing.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "A("
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeFunction %7
+          %9 = OpTypeBool
+         %10 = OpConstantFalse %9
+         %11 = OpConstant %7 1
+         %12 = OpConstant %7 2
+          %2 = OpFunction %5 None %6
+         %13 = OpLabel
+          %4 = OpFunctionCall %7 %3
+               OpReturn
+               OpFunctionEnd
+          %3 = OpFunction %7 None %8
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %10 %17 %16
+         %17 = OpLabel
+               OpReturnValue %11
+         %16 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+    const auto env = SPV_ENV_UNIVERSAL_1_5;
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    spvtools::ValidatorOptions validator_options;
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
+
+    ASSERT_FALSE(TransformationMergeFunctionReturns(3, 100, 101, 0, 0, {{}})
+                     .IsApplicable(context.get(), transformation_context));
+  }
+  {
+    // OpConstantFalse is missing.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "A("
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeFunction %7
+          %9 = OpTypeBool
+         %10 = OpConstantTrue %9
+         %11 = OpConstant %7 1
+         %12 = OpConstant %7 2
+          %2 = OpFunction %5 None %6
+         %13 = OpLabel
+          %4 = OpFunctionCall %7 %3
+               OpReturn
+               OpFunctionEnd
+          %3 = OpFunction %7 None %8
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %10 %17 %16
+         %17 = OpLabel
+               OpReturnValue %11
+         %16 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+    const auto env = SPV_ENV_UNIVERSAL_1_5;
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    spvtools::ValidatorOptions validator_options;
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
+
+    ASSERT_FALSE(TransformationMergeFunctionReturns(3, 100, 101, 0, 0, {{}})
+                     .IsApplicable(context.get(), transformation_context));
+  }
+}
+
+TEST(TransformationMergeFunctionReturnsTest, InvalidIds) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+         %42 = OpTypeFloat 32
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 0
+         %11 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %12 = OpLabel
+         %13 = OpFunctionCall %5 %14
+         %15 = OpFunctionCall %3 %16
+               OpReturn
+               OpFunctionEnd
+         %17 = OpFunction %3 None %4
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %8 %23 %20
+         %23 = OpLabel
+               OpReturn
+         %21 = OpLabel
+               OpBranch %19
+         %20 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %5 None %6
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+               OpBranchConditional %8 %30 %27
+         %30 = OpLabel
+               OpReturnValue %10
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+               OpReturnValue %11
+               OpFunctionEnd
+         %16 = OpFunction %3 None %4
+         %34 = OpLabel
+               OpBranch %35
+         %35 = OpLabel
+               OpLoopMerge %36 %37 None
+               OpBranch %38
+         %38 = OpLabel
+         %43 = OpConvertSToF %42 %10
+               OpBranchConditional %8 %39 %36
+         %39 = OpLabel
+               OpReturn
+         %37 = OpLabel
+         %44 = OpConvertSToF %42 %10
+               OpBranch %35
+         %36 = OpLabel
+         %31 = OpPhi %42 %43 %38
+         %32 = OpPhi %5 %11 %38
+               OpBranch %40
+         %40 = OpLabel
+         %41 = OpFunctionCall %3 %17
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Fresh id %100 is used twice.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 100, 100, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Fresh id %100 is used twice.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 100, 101, 0, 0, {{MakeReturnMergingInfo(20, 100, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %0 cannot be a fresh id for the new merge block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 100, 0, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %0 cannot be a fresh id for the new header block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 0, 100, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %0 cannot be a fresh id for the new |is_returning| instruction in an
+  // existing merge block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 100, 101, 0, 0, {{MakeReturnMergingInfo(20, 0, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %0 cannot be a fresh id for the new |return_val| instruction in the new
+  // return block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          14, 100, 101, 0, 10, {{MakeReturnMergingInfo(27, 102, 103, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %0 cannot be a fresh id for the new |maybe_return_val| instruction in an
+  // existing merge block, inside a non-void function.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          14, 100, 101, 102, 10, {{MakeReturnMergingInfo(27, 103, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Fresh id %102 is repeated.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          14, 100, 101, 102, 10, {{MakeReturnMergingInfo(27, 102, 104, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Id %11 (type int) does not have the correct type (float) for OpPhi
+  // instruction %31.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          16, 100, 101, 0, 0,
+          {{MakeReturnMergingInfo(36, 103, 104, {{{31, 11}, {32, 11}}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Id %11 (type int) does not have the correct type (float) for OpPhi
+  // instruction %31.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          16, 100, 101, 0, 0,
+          {{MakeReturnMergingInfo(36, 102, 0, {{{31, 11}, {32, 11}}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Id %43 is not available at the end of the entry block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          16, 100, 101, 0, 0,
+          {{MakeReturnMergingInfo(36, 102, 0, {{{31, 44}, {32, 11}}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // There is not a mapping for id %31 (float OpPhi instruction in a loop merge
+  // block) and no suitable id is available at the end of the entry block.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(
+                   16, 100, 101, 0, 0,
+                   {{MakeReturnMergingInfo(36, 102, 0, {{{32, 11}}})}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %1000 cannot be found in the module and no suitable id for OpPhi %31 is
+  // available at the end of the entry block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          16, 100, 101, 0, 0,
+          {{MakeReturnMergingInfo(36, 102, 0, {{{31, 1000}, {32, 11}}})}})
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, Simple) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypeFunction %7 %7
+          %9 = OpTypeFunction %7
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %40 = OpConstantFalse %10
+         %12 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %3 None %4
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %11 %18 %17
+         %18 = OpLabel
+               OpReturn
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %19 = OpFunction %5 None %6
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %11 %23 %24
+         %23 = OpLabel
+               OpReturnValue %12
+         %24 = OpLabel
+         %25 = OpIAdd %5 %12 %12
+               OpReturnValue %25
+         %22 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+         %26 = OpFunction %7 None %8
+         %27 = OpFunctionParameter %7
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpSelectionMerge %30 None
+               OpBranchConditional %11 %31 %30
+         %31 = OpLabel
+         %32 = OpFAdd %7 %27 %27
+               OpReturnValue %32
+         %30 = OpLabel
+               OpReturnValue %27
+               OpFunctionEnd
+         %33 = OpFunction %7 None %9
+         %34 = OpLabel
+         %35 = OpConvertSToF %7 %12
+               OpBranch %36
+         %36 = OpLabel
+               OpSelectionMerge %37 None
+               OpBranchConditional %11 %38 %37
+         %38 = OpLabel
+         %39 = OpFAdd %7 %35 %35
+               OpReturnValue %39
+         %37 = OpLabel
+               OpReturnValue %35
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // The 0s are allowed because the function's return type is void.
+  auto transformation1 =
+      TransformationMergeFunctionReturns(14, 100, 101, 0, 0, {{}});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %12 is available at the end of the entry block of %19 (it is a global
+  // variable).
+  ASSERT_TRUE(TransformationMergeFunctionReturns(19, 110, 111, 112, 12, {{}})
+                  .IsApplicable(context.get(), transformation_context));
+
+  // %1000 cannot be found in the module, but there is a suitable id available
+  // at the end of the entry block (%12).
+  auto transformation2 =
+      TransformationMergeFunctionReturns(19, 110, 111, 112, 1000, {{}});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %27 is available at the end of the entry block of %26 (it is a function
+  // parameter).
+  ASSERT_TRUE(TransformationMergeFunctionReturns(26, 120, 121, 122, 27, {{}})
+                  .IsApplicable(context.get(), transformation_context));
+
+  // %1000 cannot be found in the module, but there is a suitable id available
+  // at the end of the entry block (%27).
+  auto transformation3 =
+      TransformationMergeFunctionReturns(26, 120, 121, 122, 1000, {{}});
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // %35 is available at the end of the entry block of %33 (it is in the entry
+  // block).
+  ASSERT_TRUE(TransformationMergeFunctionReturns(26, 130, 131, 132, 27, {{}})
+                  .IsApplicable(context.get(), transformation_context));
+
+  // %1000 cannot be found in the module, but there is a suitable id available
+  // at the end of the entry block (%35).
+  auto transformation4 =
+      TransformationMergeFunctionReturns(33, 130, 131, 132, 1000, {{}});
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypeFunction %7 %7
+          %9 = OpTypeFunction %7
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %40 = OpConstantFalse %10
+         %12 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %3 None %4
+         %15 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %11 %16 %100
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %11 %18 %17
+         %18 = OpLabel
+               OpBranch %101
+         %17 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %19 = OpFunction %5 None %6
+         %20 = OpLabel
+               OpBranch %110
+        %110 = OpLabel
+               OpLoopMerge %111 %110 None
+               OpBranchConditional %11 %21 %110
+         %21 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %11 %23 %24
+         %23 = OpLabel
+               OpBranch %111
+         %24 = OpLabel
+         %25 = OpIAdd %5 %12 %12
+               OpBranch %111
+         %22 = OpLabel
+               OpUnreachable
+        %111 = OpLabel
+        %112 = OpPhi %5 %12 %23 %25 %24
+               OpReturnValue %112
+               OpFunctionEnd
+         %26 = OpFunction %7 None %8
+         %27 = OpFunctionParameter %7
+         %28 = OpLabel
+               OpBranch %120
+        %120 = OpLabel
+               OpLoopMerge %121 %120 None
+               OpBranchConditional %11 %29 %120
+         %29 = OpLabel
+               OpSelectionMerge %30 None
+               OpBranchConditional %11 %31 %30
+         %31 = OpLabel
+         %32 = OpFAdd %7 %27 %27
+               OpBranch %121
+         %30 = OpLabel
+               OpBranch %121
+        %121 = OpLabel
+        %122 = OpPhi %7 %27 %30 %32 %31
+               OpReturnValue %122
+               OpFunctionEnd
+         %33 = OpFunction %7 None %9
+         %34 = OpLabel
+         %35 = OpConvertSToF %7 %12
+               OpBranch %130
+        %130 = OpLabel
+               OpLoopMerge %131 %130 None
+               OpBranchConditional %11 %36 %130
+         %36 = OpLabel
+               OpSelectionMerge %37 None
+               OpBranchConditional %11 %38 %37
+         %38 = OpLabel
+         %39 = OpFAdd %7 %35 %35
+               OpBranch %131
+         %37 = OpLabel
+               OpBranch %131
+        %131 = OpLabel
+        %132 = OpPhi %7 %35 %37 %39 %38
+               OpReturnValue %132
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, NestedLoops) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 2
+         %11 = OpConstant %5 1
+         %12 = OpConstant %5 3
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %5 None %6
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %17 %16 None
+               OpBranchConditional %8 %18 %16
+         %18 = OpLabel
+               OpLoopMerge %19 %20 None
+               OpBranchConditional %8 %19 %21
+         %19 = OpLabel
+               OpBranch %17
+         %21 = OpLabel
+               OpReturnValue %12
+         %17 = OpLabel
+               OpBranch %22
+         %20 = OpLabel
+               OpBranch %18
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranchConditional %8 %26 %23
+         %26 = OpLabel
+               OpSelectionMerge %27 None
+               OpBranchConditional %9 %28 %27
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpLoopMerge %30 %29 None
+               OpBranchConditional %8 %30 %29
+         %30 = OpLabel
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+               OpBranchConditional %9 %34 %31
+         %34 = OpLabel
+               OpReturnValue %10
+         %32 = OpLabel
+               OpBranch %30
+         %31 = OpLabel
+         %35 = OpPhi %5 %11 %33
+         %36 = OpPhi %5 %10 %33
+               OpBranch %37
+         %37 = OpLabel
+               OpReturnValue %35
+         %27 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+               OpBranch %38
+         %38 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  auto transformation = TransformationMergeFunctionReturns(
+      14, 100, 101, 102, 11,
+      {{MakeReturnMergingInfo(19, 103, 104, {{}}),
+        MakeReturnMergingInfo(17, 105, 106, {{}}),
+        MakeReturnMergingInfo(31, 107, 108, {{{35, 10}, {36, 12}}}),
+        MakeReturnMergingInfo(23, 109, 110, {})}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 2
+         %11 = OpConstant %5 1
+         %12 = OpConstant %5 3
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %5 None %6
+         %15 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %8 %16 %100
+         %16 = OpLabel
+               OpLoopMerge %17 %16 None
+               OpBranchConditional %8 %18 %16
+         %18 = OpLabel
+               OpLoopMerge %19 %20 None
+               OpBranchConditional %8 %19 %21
+         %19 = OpLabel
+        %103 = OpPhi %7 %8 %21 %9 %18
+        %104 = OpPhi %5 %12 %21 %11 %18
+               OpBranch %17
+         %21 = OpLabel
+               OpBranch %19
+         %17 = OpLabel
+        %105 = OpPhi %7 %103 %19
+        %106 = OpPhi %5 %104 %19
+               OpBranchConditional %105 %101 %22
+         %20 = OpLabel
+               OpBranch %18
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranchConditional %8 %26 %23
+         %26 = OpLabel
+               OpSelectionMerge %27 None
+               OpBranchConditional %9 %28 %27
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpLoopMerge %30 %29 None
+               OpBranchConditional %8 %30 %29
+         %30 = OpLabel
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+               OpBranchConditional %9 %34 %31
+         %34 = OpLabel
+               OpBranch %31
+         %32 = OpLabel
+               OpBranch %30
+         %31 = OpLabel
+        %107 = OpPhi %7 %8 %34 %9 %33
+        %108 = OpPhi %5 %10 %34 %11 %33
+         %35 = OpPhi %5 %11 %33 %10 %34
+         %36 = OpPhi %5 %10 %33 %12 %34
+               OpBranchConditional %107 %23 %37
+         %37 = OpLabel
+               OpBranch %23
+         %27 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+        %109 = OpPhi %7 %107 %31 %8 %37 %9 %25
+        %110 = OpPhi %5 %108 %31 %35 %37 %11 %25
+               OpBranchConditional %109 %101 %38
+         %38 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %5 %106 %17 %110 %23 %12 %38
+               OpReturnValue %102
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, OverflowIds) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %5 None %6
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpIAdd %5 %10 %10
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+               OpBranchConditional %8 %19 %16
+         %19 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %9 %21 %20
+         %21 = OpLabel
+               OpReturnValue %10
+         %20 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranchConditional %8 %14 %16
+         %16 = OpLabel
+         %22 = OpPhi %5 %15 %17 %10 %18
+               OpBranch %23
+         %23 = OpLabel
+               OpReturnValue %22
+               OpFunctionEnd
+         %24 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+               OpBranchConditional %8 %30 %27
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %9 %32 %31
+         %32 = OpLabel
+               OpReturn
+         %31 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+         %33 = OpPhi %5 %10 %29
+               OpBranch %34
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  auto overflow_ids_unique_ptr = MakeUnique<CounterOverflowIdSource>(1000);
+  auto overflow_ids_ptr = overflow_ids_unique_ptr.get();
+  TransformationContext transformation_context_with_overflow_ids(
+      MakeUnique<FactManager>(context.get()), validator_options,
+      std::move(overflow_ids_unique_ptr));
+
+  // No mapping from merge block %16 to fresh ids is given, so overflow ids are
+  // needed.
+  auto transformation1 =
+      TransformationMergeFunctionReturns(12, 100, 101, 102, 10, {{}});
+
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      transformation1.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  ASSERT_TRUE(transformation1.IsApplicable(
+      context.get(), transformation_context_with_overflow_ids));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context_with_overflow_ids,
+                        overflow_ids_ptr->GetIssuedOverflowIds());
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // No mapping from merge block %27 to fresh ids is given, so overflow ids are
+  // needed.
+  auto transformation2 =
+      TransformationMergeFunctionReturns(24, 110, 111, 0, 0, {{}});
+
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      transformation2.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  ASSERT_TRUE(transformation2.IsApplicable(
+      context.get(), transformation_context_with_overflow_ids));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context_with_overflow_ids, {1002});
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %5 None %6
+         %13 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %8 %14 %100
+         %14 = OpLabel
+         %15 = OpIAdd %5 %10 %10
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+               OpBranchConditional %8 %19 %16
+         %19 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %9 %21 %20
+         %21 = OpLabel
+               OpBranch %16
+         %20 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranchConditional %8 %14 %16
+         %16 = OpLabel
+       %1000 = OpPhi %7 %8 %21 %9 %17 %9 %18
+       %1001 = OpPhi %5 %10 %21 %10 %17 %10 %18
+         %22 = OpPhi %5 %15 %17 %10 %18 %10 %21
+               OpBranchConditional %1000 %101 %23
+         %23 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %5 %1001 %16 %22 %23
+               OpReturnValue %102
+               OpFunctionEnd
+         %24 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %110
+        %110 = OpLabel
+               OpLoopMerge %111 %110 None
+               OpBranchConditional %8 %26 %110
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+               OpBranchConditional %8 %30 %27
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %9 %32 %31
+         %32 = OpLabel
+               OpBranch %27
+         %31 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+       %1002 = OpPhi %7 %8 %32 %9 %29
+         %33 = OpPhi %5 %10 %29 %10 %32
+               OpBranchConditional %1002 %111 %34
+         %34 = OpLabel
+               OpBranch %111
+        %111 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, MissingIdsForOpPhi) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpTypeFunction %3 %8
+         %10 = OpTypeFloat 32
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %3 None %9
+         %13 = OpFunctionParameter %8
+         %14 = OpLabel
+         %15 = OpConvertSToF %10 %13
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %17 %18 None
+               OpBranch %19
+         %19 = OpLabel
+               OpBranchConditional %6 %20 %17
+         %20 = OpLabel
+               OpSelectionMerge %21 None
+               OpBranchConditional %7 %22 %21
+         %22 = OpLabel
+               OpReturn
+         %21 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %23 = OpPhi %8 %13 %19
+         %24 = OpPhi %10 %15 %19
+         %25 = OpPhi %5 %6 %19
+               OpBranch %26
+         %26 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // This test checks whether the transformation is able to find suitable ids
+  // to use in existing OpPhi instructions if they are not provided in the
+  // corresponding mapping.
+
+  auto transformation = TransformationMergeFunctionReturns(
+      12, 101, 102, 0, 0,
+      {{MakeReturnMergingInfo(17, 103, 0, {{{25, 7}, {35, 8}}})}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpTypeFunction %3 %8
+         %10 = OpTypeFloat 32
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %3 None %9
+         %13 = OpFunctionParameter %8
+         %14 = OpLabel
+         %15 = OpConvertSToF %10 %13
+               OpBranch %101
+        %101 = OpLabel
+               OpLoopMerge %102 %101 None
+               OpBranchConditional %6 %16 %101
+         %16 = OpLabel
+               OpLoopMerge %17 %18 None
+               OpBranch %19
+         %19 = OpLabel
+               OpBranchConditional %6 %20 %17
+         %20 = OpLabel
+               OpSelectionMerge %21 None
+               OpBranchConditional %7 %22 %21
+         %22 = OpLabel
+               OpBranch %17
+         %21 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+        %103 = OpPhi %5 %6 %22 %7 %19
+         %23 = OpPhi %8 %13 %19 %13 %22
+         %24 = OpPhi %10 %15 %19 %15 %22
+         %25 = OpPhi %5 %6 %19 %7 %22
+               OpBranchConditional %103 %102 %26
+         %26 = OpLabel
+               OpBranch %102
+        %102 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules1) {
+  // An id defined in a loop is used in the corresponding merge block. After the
+  // transformation, the id will not dominate the merge block anymore. This is
+  // only OK if the use is inside an OpPhi instruction. (Note that there is also
+  // another condition for this transformation that forbids non-OpPhi
+  // instructions in relevant merge blocks, but that case is also considered
+  // here for completeness).
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+         %16 = OpCopyObject %5 %15
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %7 %24 %25
+         %25 = OpLabel
+               OpReturn
+         %24 = OpLabel
+         %26 = OpCopyObject %5 %7
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %7 %20 %21
+         %21 = OpLabel
+         %27 = OpPhi %5 %26 %22
+               OpBranch %28
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // In function %2, the definition of id %15 will not dominate its use in
+  // instruction %16 (inside merge block %10) after a new branch from return
+  // block %14 is added.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // In function %18, The definition of id %26 will still dominate its use in
+  // instruction %27 (inside merge block %21), because %27 is an OpPhi
+  // instruction.
+  auto transformation = TransformationMergeFunctionReturns(
+      18, 100, 101, 0, 0, {{MakeReturnMergingInfo(21, 102, 103, {{}})}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+         %16 = OpCopyObject %5 %15
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %20 %100
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %7 %24 %25
+         %25 = OpLabel
+               OpBranch %21
+         %24 = OpLabel
+         %26 = OpCopyObject %5 %7
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %7 %20 %21
+         %21 = OpLabel
+        %102 = OpPhi %5 %6 %25 %7 %22
+         %27 = OpPhi %5 %26 %22 %6 %25
+               OpBranchConditional %102 %101 %28
+         %28 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules2) {
+  // An id defined in a loop is used after the corresponding merge block. After
+  // the transformation, the id will not dominate its use anymore, regardless of
+  // the kind of instruction in which it is used.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpCopyObject %5 %15
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %7 %24 %25
+         %25 = OpLabel
+               OpReturn
+         %24 = OpLabel
+         %26 = OpCopyObject %5 %7
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %7 %20 %21
+         %21 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+         %28 = OpPhi %5 %26 %21
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // In function %2, the definition of id %15 will not dominate its use in
+  // instruction %17 (inside block %16) after a new branch from return
+  // block %14 to merge block %10 is added.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // In function %18, the definition of id %26 will not dominate its use in
+  // instruction %28 (inside block %27) after a new branch from return
+  // block %25 to merge block %21 is added.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(
+                   2, 100, 101, 0, 0,
+                   {{MakeReturnMergingInfo(10, 102, 0, {{}}),
+                     MakeReturnMergingInfo(21, 103, 0, {{}})}})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules3) {
+  // An id defined in a loop is used inside the loop.
+  // Changes to the predecessors of the merge block do not affect the validity
+  // of the uses of such id.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+         %18 = OpPhi %5 %15 %13
+         %19 = OpCopyObject %5 %15
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // In function %2, the definition of id %15 will still dominate its use in
+  // instructions %18 and %19 after the transformation is applied, because the
+  // fact that the id definition dominates the uses does not depend on it
+  // dominating the merge block.
+  auto transformation = TransformationMergeFunctionReturns(
+      2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %9 %100
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpBranch %10
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+         %18 = OpPhi %5 %15 %13
+         %19 = OpCopyObject %5 %15
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+        %102 = OpPhi %5 %6 %14 %7 %11
+               OpBranchConditional %102 %101 %20
+         %20 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules4) {
+  // An id defined in a loop, which contain 2 return statements, is used after
+  // the loop. We can only apply the transformation if the id dominates all of
+  // the return blocks.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpCopyObject %5 %7
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %14 %15
+         %15 = OpLabel
+               OpReturn
+         %14 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+               OpReturn
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %19 = OpCopyObject %5 %13
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %3 None %4
+         %21 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %7 %26 %27
+         %27 = OpLabel
+               OpReturn
+         %26 = OpLabel
+         %28 = OpCopyObject %5 %7
+               OpSelectionMerge %29 None
+               OpBranchConditional %7 %29 %30
+         %30 = OpLabel
+               OpReturn
+         %29 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranchConditional %7 %22 %23
+         %23 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpCopyObject %5 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // In function %2, the definition of id %13 will still dominate its use in
+  // instruction %19 after the transformation is applied, because %13 dominates
+  // all of the return blocks.
+  auto transformation = TransformationMergeFunctionReturns(
+      2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // In function %20, the definition of id %28 will not dominate its use in
+  // instruction %32 after the transformation is applied, because %28 dominates
+  // only one of the return blocks.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          20, 100, 101, 0, 0, {{MakeReturnMergingInfo(23, 102, 103, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %9 %100
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpCopyObject %5 %7
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %14 %15
+         %15 = OpLabel
+               OpBranch %10
+         %14 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+               OpBranch %10
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+        %102 = OpPhi %5 %6 %15 %6 %17 %7 %11
+               OpBranchConditional %102 %101 %18
+         %18 = OpLabel
+         %19 = OpCopyObject %5 %13
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %3 None %4
+         %21 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %7 %26 %27
+         %27 = OpLabel
+               OpReturn
+         %26 = OpLabel
+         %28 = OpCopyObject %5 %7
+               OpSelectionMerge %29 None
+               OpBranchConditional %7 %29 %30
+         %30 = OpLabel
+               OpReturn
+         %29 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranchConditional %7 %22 %23
+         %23 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpCopyObject %5 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
\ No newline at end of file