spirv-fuzz: Add inline function transformation (#3517)

Fixes #3505.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index db32902..5828d20 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -66,6 +66,7 @@
         fuzzer_pass_construct_composites.h
         fuzzer_pass_copy_objects.h
         fuzzer_pass_donate_modules.h
+        fuzzer_pass_inline_functions.h
         fuzzer_pass_invert_comparison_operators.h
         fuzzer_pass_interchange_signedness_of_integer_operands.h
         fuzzer_pass_interchange_zero_like_constants.h
@@ -137,6 +138,7 @@
         transformation_context.h
         transformation_equation_instruction.h
         transformation_function_call.h
+        transformation_inline_function.h
         transformation_invert_comparison_operator.h
         transformation_load.h
         transformation_make_vector_operation_dynamic.h
@@ -208,6 +210,7 @@
         fuzzer_pass_construct_composites.cpp
         fuzzer_pass_copy_objects.cpp
         fuzzer_pass_donate_modules.cpp
+        fuzzer_pass_inline_functions.cpp
         fuzzer_pass_invert_comparison_operators.cpp
         fuzzer_pass_interchange_signedness_of_integer_operands.cpp
         fuzzer_pass_interchange_zero_like_constants.cpp
@@ -278,6 +281,7 @@
         transformation_context.cpp
         transformation_equation_instruction.cpp
         transformation_function_call.cpp
+        transformation_inline_function.cpp
         transformation_invert_comparison_operator.cpp
         transformation_load.cpp
         transformation_make_vector_operation_dynamic.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 651db36..70e3230 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -49,6 +49,7 @@
 #include "source/fuzz/fuzzer_pass_construct_composites.h"
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/fuzzer_pass_inline_functions.h"
 #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
 #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
@@ -282,6 +283,9 @@
     MaybeAddPass<FuzzerPassDonateModules>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out, donor_suppliers);
+    MaybeAddPass<FuzzerPassInlineFunctions>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassInvertComparisonOperators>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index a187daa..ee8db86 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -69,6 +69,7 @@
     30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
     {50, 95};
+const std::pair<uint32_t, uint32_t> kChanceOfInliningFunction = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
     10, 90};
 const std::pair<uint32_t, uint32_t>
@@ -216,6 +217,8 @@
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
   chance_of_going_deeper_when_making_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+  chance_of_inlining_function_ =
+      ChooseBetweenMinAndMax(kChanceOfInliningFunction);
   chance_of_interchanging_signedness_of_integer_operands_ =
       ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands);
   chance_of_interchanging_zero_like_constants_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index c501614..9f85b13 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -195,6 +195,9 @@
   uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
     return chance_of_going_deeper_when_making_access_chain_;
   }
+  uint32_t GetChanceOfInliningFunction() {
+    return chance_of_inlining_function_;
+  }
   uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() {
     return chance_of_interchanging_signedness_of_integer_operands_;
   }
@@ -383,6 +386,7 @@
   uint32_t chance_of_donating_additional_module_;
   uint32_t chance_of_going_deeper_to_insert_in_composite_;
   uint32_t chance_of_going_deeper_when_making_access_chain_;
+  uint32_t chance_of_inlining_function_;
   uint32_t chance_of_interchanging_signedness_of_integer_operands_;
   uint32_t chance_of_interchanging_zero_like_constants_;
   uint32_t chance_of_inverting_comparison_operators_;
diff --git a/source/fuzz/fuzzer_pass_inline_functions.cpp b/source/fuzz/fuzzer_pass_inline_functions.cpp
new file mode 100644
index 0000000..90160d8
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_inline_functions.cpp
@@ -0,0 +1,104 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_inline_functions.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_inline_function.h"
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassInlineFunctions::FuzzerPassInlineFunctions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default;
+
+void FuzzerPassInlineFunctions::Apply() {
+  // |function_call_instructions| are the instructions that will be inlined.
+  // First, they will be collected and then do the inlining in another loop.
+  // This avoids changing the module while it is being inspected.
+  std::vector<opt::Instruction*> function_call_instructions;
+
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfInliningFunction())) {
+          continue;
+        }
+
+        // |instruction| must be suitable for inlining.
+        if (!TransformationInlineFunction::IsSuitableForInlining(
+                GetIRContext(), &instruction)) {
+          continue;
+        }
+
+        function_call_instructions.push_back(&instruction);
+      }
+    }
+  }
+
+  // Once the function calls have been collected, it's time to actually create
+  // and apply the inlining transformations.
+  for (auto& function_call_instruction : function_call_instructions) {
+    // If |function_call_instruction| is not the penultimate instruction in its
+    // block or its block termination instruction is not OpBranch, then try to
+    // split |function_call_block| such that the conditions are met.
+    auto* function_call_block =
+        GetIRContext()->get_instr_block(function_call_instruction);
+    if ((function_call_instruction != &*--function_call_block->tail() ||
+         function_call_block->terminator()->opcode() != SpvOpBranch) &&
+        !MaybeApplyTransformation(TransformationSplitBlock(
+            MakeInstructionDescriptor(GetIRContext(),
+                                      function_call_instruction->NextNode()),
+            GetFuzzerContext()->GetFreshId()))) {
+      continue;
+    }
+
+    auto* called_function = fuzzerutil::FindFunction(
+        GetIRContext(), function_call_instruction->GetSingleWordInOperand(0));
+
+    // Mapping the called function instructions.
+    std::map<uint32_t, uint32_t> result_id_map;
+    for (auto& called_function_block : *called_function) {
+      // The called function entry block label will not be inlined.
+      if (&called_function_block != &*called_function->entry()) {
+        result_id_map[called_function_block.GetLabelInst()->result_id()] =
+            GetFuzzerContext()->GetFreshId();
+      }
+
+      for (auto& instruction_to_inline : called_function_block) {
+        // The instructions are mapped to fresh ids.
+        if (instruction_to_inline.HasResultId()) {
+          result_id_map[instruction_to_inline.result_id()] =
+              GetFuzzerContext()->GetFreshId();
+        }
+      }
+    }
+
+    // Applies the inline function transformation.
+    ApplyTransformation(TransformationInlineFunction(
+        function_call_instruction->result_id(), result_id_map));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_inline_functions.h b/source/fuzz/fuzzer_pass_inline_functions.h
new file mode 100644
index 0000000..37295d1
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_inline_functions.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_INLINE_FUNCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Looks for OpFunctionCall instructions and randomly decides which ones to
+// inline. If the instructions of the called function are going to be inlined,
+// then a mapping, between their result ids and suitable ids, is done.
+class FuzzerPassInlineFunctions : public FuzzerPass {
+ public:
+  FuzzerPassInlineFunctions(opt::IRContext* ir_context,
+                            TransformationContext* transformation_context,
+                            FuzzerContext* fuzzer_context,
+                            protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassInlineFunctions() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 225aa4f..c20fa99 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -413,6 +413,7 @@
     TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
     TransformationPropagateInstructionUp propagate_instruction_up = 67;
     TransformationCompositeInsert composite_insert = 68;
+    TransformationInlineFunction inline_function = 69;
     // Add additional option using the next available number.
   }
 }
@@ -1103,6 +1104,20 @@
 
 }
 
+message TransformationInlineFunction {
+
+  // This transformation inlines a function by mapping the function instructions to fresh ids.
+
+  // Result id of the function call instruction.
+  uint32 function_call_id = 1;
+
+  // For each result id defined by the called function,
+  // this map provides an associated fresh id that can
+  // be used in the inlined version of the function call.
+  repeated UInt32Pair result_id_map = 2;
+
+}
+
 message TransformationInvertComparisonOperator {
 
   // For some instruction with result id |operator_id| that
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index ec765b3..0df47d5 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -53,6 +53,7 @@
 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
 #include "source/fuzz/transformation_equation_instruction.h"
 #include "source/fuzz/transformation_function_call.h"
+#include "source/fuzz/transformation_inline_function.h"
 #include "source/fuzz/transformation_invert_comparison_operator.h"
 #include "source/fuzz/transformation_load.h"
 #include "source/fuzz/transformation_make_vector_operation_dynamic.h"
@@ -192,6 +193,9 @@
           message.equation_instruction());
     case protobufs::Transformation::TransformationCase::kFunctionCall:
       return MakeUnique<TransformationFunctionCall>(message.function_call());
+    case protobufs::Transformation::TransformationCase::kInlineFunction:
+      return MakeUnique<TransformationInlineFunction>(
+          message.inline_function());
     case protobufs::Transformation::TransformationCase::
         kInvertComparisonOperator:
       return MakeUnique<TransformationInvertComparisonOperator>(
diff --git a/source/fuzz/transformation_inline_function.cpp b/source/fuzz/transformation_inline_function.cpp
new file mode 100644
index 0000000..f244f0d
--- /dev/null
+++ b/source/fuzz/transformation_inline_function.cpp
@@ -0,0 +1,281 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_inline_function.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationInlineFunction::TransformationInlineFunction(
+    const spvtools::fuzz::protobufs::TransformationInlineFunction& message)
+    : message_(message) {}
+
+TransformationInlineFunction::TransformationInlineFunction(
+    uint32_t function_call_id,
+    const std::map<uint32_t, uint32_t>& result_id_map) {
+  message_.set_function_call_id(function_call_id);
+  *message_.mutable_result_id_map() =
+      fuzzerutil::MapToRepeatedUInt32Pair(result_id_map);
+}
+
+bool TransformationInlineFunction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The values in the |message_.result_id_map| must be all fresh and all
+  // distinct.
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+  std::set<uint32_t> ids_used_by_this_transformation;
+  for (auto& pair : result_id_map) {
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+            pair.second, ir_context, &ids_used_by_this_transformation)) {
+      return false;
+    }
+  }
+
+  // |function_call_instruction| must be suitable for inlining.
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  if (!IsSuitableForInlining(ir_context, function_call_instruction)) {
+    return false;
+  }
+
+  // |function_call_instruction| must be the penultimate instruction in its
+  // block and its block termination instruction must be an OpBranch. This
+  // avoids the case where the penultimate instruction is an OpLoopMerge, which
+  // would make the back-edge block not branch to the loop header.
+  auto* function_call_instruction_block =
+      ir_context->get_instr_block(function_call_instruction);
+  if (function_call_instruction !=
+          &*--function_call_instruction_block->tail() ||
+      function_call_instruction_block->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  for (auto& block : *called_function) {
+    // Since the entry block label will not be inlined, only the remaining
+    // labels must have a corresponding value in the map.
+    if (&block != &*called_function->entry() &&
+        !result_id_map.count(block.GetLabel()->result_id())) {
+      return false;
+    }
+
+    // |result_id_map| must have an entry for every result id in the called
+    // function.
+    for (auto& instruction : block) {
+      // If |instruction| has result id, then it must have a mapped id in
+      // |result_id_map|.
+      if (instruction.HasResultId() &&
+          !result_id_map.count(instruction.result_id())) {
+        return false;
+      }
+    }
+  }
+
+  // |result_id_map| must not contain an entry for any parameter of the function
+  // that is being inlined.
+  bool found_entry_for_parameter = false;
+  called_function->ForEachParam(
+      [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) {
+        if (result_id_map.count(param->result_id())) {
+          found_entry_for_parameter = true;
+        }
+      });
+  return !found_entry_for_parameter;
+}
+
+void TransformationInlineFunction::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  auto* caller_function =
+      ir_context->get_instr_block(function_call_instruction)->GetParent();
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+  auto* successor_block = ir_context->cfg()->block(
+      ir_context->get_instr_block(function_call_instruction)
+          ->terminator()
+          ->GetSingleWordInOperand(0));
+
+  // Inline the |called_function| entry block.
+  for (auto& entry_block_instruction : *called_function->entry()) {
+    opt::Instruction* inlined_instruction = nullptr;
+
+    if (entry_block_instruction.opcode() == SpvOpVariable) {
+      // All OpVariable instructions in a function must be in the first block
+      // in the function.
+      inlined_instruction = caller_function->begin()->begin()->InsertBefore(
+          MakeUnique<opt::Instruction>(entry_block_instruction));
+    } else {
+      inlined_instruction = function_call_instruction->InsertBefore(
+          MakeUnique<opt::Instruction>(entry_block_instruction));
+    }
+
+    AdaptInlinedInstruction(ir_context, inlined_instruction);
+  }
+
+  // Inline the |called_function| non-entry blocks.
+  for (auto& block : *called_function) {
+    if (&block == &*called_function->entry()) {
+      continue;
+    }
+
+    auto* cloned_block = block.Clone(ir_context);
+    cloned_block = caller_function->InsertBasicBlockBefore(
+        std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block);
+    cloned_block->SetParent(caller_function);
+    cloned_block->GetLabel()->SetResultId(
+        result_id_map.at(cloned_block->GetLabel()->result_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    cloned_block->GetLabel()->result_id());
+
+    for (auto& inlined_instruction : *cloned_block) {
+      AdaptInlinedInstruction(ir_context, &inlined_instruction);
+    }
+  }
+
+  // Removes the function call instruction and its block termination instruction
+  // from |caller_function|.
+  ir_context->KillInst(
+      ir_context->get_instr_block(function_call_instruction)->terminator());
+  ir_context->KillInst(function_call_instruction);
+
+  // Since the SPIR-V module has changed, no analyses must be validated.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationInlineFunction::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_inline_function() = message_;
+  return result;
+}
+
+bool TransformationInlineFunction::IsSuitableForInlining(
+    opt::IRContext* ir_context, opt::Instruction* function_call_instruction) {
+  // |function_call_instruction| must be defined and must be an OpFunctionCall
+  // instruction.
+  if (!function_call_instruction ||
+      function_call_instruction->opcode() != SpvOpFunctionCall) {
+    return false;
+  }
+
+  // If |function_call_instruction| return type is void, then
+  // |function_call_instruction| must not have uses.
+  if (ir_context->get_type_mgr()
+          ->GetType(function_call_instruction->type_id())
+          ->AsVoid() &&
+      ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) {
+    return false;
+  }
+
+  // |called_function| must not have an early return.
+  auto called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  if (called_function->HasEarlyReturn()) {
+    return false;
+  }
+
+  // |called_function| must not use OpKill or OpUnreachable.
+  if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationInlineFunction::AdaptInlinedInstruction(
+    opt::IRContext* ir_context,
+    opt::Instruction* instruction_to_be_inlined) const {
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+
+  // Replaces the operand ids with their mapped result ids.
+  instruction_to_be_inlined->ForEachInId([called_function,
+                                          function_call_instruction,
+                                          &result_id_map](uint32_t* id) {
+    // If |id| is mapped, then set it to its mapped value.
+    if (result_id_map.count(*id)) {
+      *id = result_id_map.at(*id);
+      return;
+    }
+
+    uint32_t parameter_index = 0;
+    called_function->ForEachParam(
+        [id, function_call_instruction,
+         &parameter_index](opt::Instruction* parameter_instruction) {
+          // If the id is a function parameter, then set it to the
+          // parameter value passed in the function call instruction.
+          if (*id == parameter_instruction->result_id()) {
+            // We do + 1 because the first in-operand for OpFunctionCall is
+            // the function id that is being called.
+            *id = function_call_instruction->GetSingleWordInOperand(
+                parameter_index + 1);
+          }
+          parameter_index++;
+        });
+  });
+
+  // If |instruction_to_be_inlined| has result id, then set it to its mapped
+  // value.
+  if (instruction_to_be_inlined->HasResultId()) {
+    assert(result_id_map.count(instruction_to_be_inlined->result_id()) &&
+           "Result id must be mapped to a fresh id.");
+    instruction_to_be_inlined->SetResultId(
+        result_id_map.at(instruction_to_be_inlined->result_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    instruction_to_be_inlined->result_id());
+  }
+
+  // The return instruction will be changed into an OpBranch to the basic
+  // block that follows the block containing the function call.
+  if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) {
+    uint32_t successor_block_id =
+        ir_context->get_instr_block(function_call_instruction)
+            ->terminator()
+            ->GetSingleWordInOperand(0);
+    switch (instruction_to_be_inlined->opcode()) {
+      case SpvOpReturn:
+        instruction_to_be_inlined->AddOperand(
+            {SPV_OPERAND_TYPE_ID, {successor_block_id}});
+        break;
+      case SpvOpReturnValue: {
+        instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpCopyObject, function_call_instruction->type_id(),
+            function_call_instruction->result_id(),
+            opt::Instruction::OperandList(
+                {{SPV_OPERAND_TYPE_ID,
+                  {instruction_to_be_inlined->GetSingleWordOperand(0)}}})));
+        instruction_to_be_inlined->SetInOperand(0, {successor_block_id});
+        break;
+      }
+      default:
+        break;
+    }
+    instruction_to_be_inlined->SetOpcode(SpvOpBranch);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_inline_function.h b/source/fuzz/transformation_inline_function.h
new file mode 100644
index 0000000..29a9ea6
--- /dev/null
+++ b/source/fuzz/transformation_inline_function.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_INLINE_FUNCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationInlineFunction : public Transformation {
+ public:
+  explicit TransformationInlineFunction(
+      const protobufs::TransformationInlineFunction& message);
+
+  TransformationInlineFunction(
+      uint32_t function_call_id,
+      const std::map<uint32_t, uint32_t>& result_id_map);
+
+  // - |message_.result_id_map| must map the instructions of the called function
+  //   to fresh ids.
+  // - |message_.function_call_id| must be an OpFunctionCall instruction.
+  //   It must not have an early return and must not use OpUnreachable or
+  //   OpKill. This is to guard against making the module invalid when the
+  //   caller is inside a continue construct.
+  //   TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3735):
+  //     Allow functions that use OpKill or OpUnreachable to be inlined if the
+  //     function call is not part of a continue construct.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the OpFunctionCall instruction, identified by
+  // |message_.function_call_id|, with a copy of the function's body.
+  // |message_.result_id_map| is used to provide fresh ids for duplicate
+  // instructions.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if |function_call_instruction| is defined, is an
+  // OpFunctionCall instruction, has no uses if its return type is void, has no
+  // early returns and has no uses of OpKill or OpUnreachable.
+  static bool IsSuitableForInlining(
+      opt::IRContext* ir_context, opt::Instruction* function_call_instruction);
+
+ private:
+  protobufs::TransformationInlineFunction message_;
+
+  // Inline |instruction_to_be_inlined| by setting its ids to the corresponding
+  // ids in |result_id_map|.
+  void AdaptInlinedInstruction(opt::IRContext* ir_context,
+                               opt::Instruction* instruction) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 3b64567..7204f69 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -61,6 +61,7 @@
           transformation_compute_data_synonym_fact_closure_test.cpp
           transformation_equation_instruction_test.cpp
           transformation_function_call_test.cpp
+          transformation_inline_function_test.cpp
           transformation_invert_comparison_operator_test.cpp
           transformation_load_test.cpp
           transformation_make_vector_operation_dynamic_test.cpp
diff --git a/test/fuzz/transformation_inline_function_test.cpp b/test/fuzz/transformation_inline_function_test.cpp
new file mode 100644
index 0000000..694f19c
--- /dev/null
+++ b/test/fuzz/transformation_inline_function_test.cpp
@@ -0,0 +1,756 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// 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_inline_function.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationInlineFunctionTest, IsApplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %52 "main"
+               OpExecutionMode %52 OriginUpperLeft
+               OpName %56 "function_with_void_return"
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeFloat 32
+          %4 = OpTypeVector %3 4
+          %5 = OpTypePointer Function %4
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFunction %3 %5 %5
+
+; Constant scalars
+          %9 = OpConstant %3 1
+         %10 = OpConstant %3 2
+         %11 = OpConstant %3 3
+         %12 = OpConstant %3 4
+         %13 = OpConstant %3 5
+         %14 = OpConstant %3 6
+         %15 = OpConstant %3 7
+         %16 = OpConstant %3 8
+         %17 = OpConstantTrue %2
+
+; Constant vectors
+         %18 = OpConstantComposite %4 %9 %10 %11 %12
+         %19 = OpConstantComposite %4 %13 %14 %15 %16
+
+; function with void return
+         %20 = OpFunction %6 None %7
+         %21 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+; function with early return
+         %22 = OpFunction %6 None %7
+         %23 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %17 %24 %25
+         %24 = OpLabel
+               OpReturn
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+; function containing an OpKill instruction
+         %27 = OpFunction %6 None %7
+         %28 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+; function containing an OpUnreachable instruction
+         %29 = OpFunction %6 None %7
+         %30 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+
+; dot product function
+         %31 = OpFunction %3 None %8
+         %32 = OpFunctionParameter %5
+         %33 = OpFunctionParameter %5
+         %34 = OpLabel
+         %35 = OpLoad %4 %32
+         %36 = OpLoad %4 %33
+         %37 = OpCompositeExtract %3 %35 0
+         %38 = OpCompositeExtract %3 %36 0
+         %39 = OpFMul %3 %37 %38
+         %40 = OpCompositeExtract %3 %35 1
+         %41 = OpCompositeExtract %3 %36 1
+         %42 = OpFMul %3 %40 %41
+         %43 = OpCompositeExtract %3 %35 2
+         %44 = OpCompositeExtract %3 %36 2
+         %45 = OpFMul %3 %43 %44
+         %46 = OpCompositeExtract %3 %35 3
+         %47 = OpCompositeExtract %3 %36 3
+         %48 = OpFMul %3 %46 %47
+         %49 = OpFAdd %3 %39 %42
+         %50 = OpFAdd %3 %45 %49
+         %51 = OpFAdd %3 %48 %50
+               OpReturnValue %51
+               OpFunctionEnd
+
+; main function
+         %52 = OpFunction %6 None %7
+         %53 = OpLabel
+         %54 = OpVariable %5 Function
+         %55 = OpVariable %5 Function
+         %56 = OpFunctionCall %6 %20 ; function with void return
+               OpBranch %57
+         %57 = OpLabel
+         %59 = OpFunctionCall %6 %22 ; function with early return
+               OpBranch %60
+         %60 = OpLabel
+         %61 = OpFunctionCall %6 %27 ; function containing OpKill
+               OpBranch %62
+         %62 = OpLabel
+         %63 = OpFunctionCall %6 %29 ; function containing OpUnreachable
+               OpBranch %64
+         %64 = OpLabel
+               OpStore %54 %18
+               OpStore %55 %19
+         %65 = OpFunctionCall %3 %31 %54 %55 ; dot product function
+               OpBranch %66
+         %66 = 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()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Tests undefined OpFunctionCall instruction.
+  auto transformation = TransformationInlineFunction(67, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests false OpFunctionCall instruction.
+  transformation = TransformationInlineFunction(42, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests use of called function with void return.
+  transformation = TransformationInlineFunction(56, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests called function having an early return.
+  transformation =
+      TransformationInlineFunction(59, {{24, 67}, {25, 68}, {26, 69}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests called function containing an OpKill instruction.
+  transformation = TransformationInlineFunction(61, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests called function containing an OpUnreachable instruction.
+  transformation = TransformationInlineFunction(63, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable transformation.
+  transformation = TransformationInlineFunction(65, {{35, 67},
+                                                     {36, 68},
+                                                     {37, 69},
+                                                     {38, 70},
+                                                     {39, 71},
+                                                     {40, 72},
+                                                     {41, 73},
+                                                     {42, 74},
+                                                     {43, 75},
+                                                     {44, 76},
+                                                     {45, 77},
+                                                     {46, 78},
+                                                     {47, 79},
+                                                     {48, 80},
+                                                     {49, 81},
+                                                     {50, 82},
+                                                     {51, 83}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationInlineFunctionTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %39 "main"
+
+; Types
+          %2 = OpTypeFloat 32
+          %3 = OpTypeVector %2 4
+          %4 = OpTypePointer Function %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFunction %2 %4 %4
+
+; Constant scalars
+          %8 = OpConstant %2 1
+          %9 = OpConstant %2 2
+         %10 = OpConstant %2 3
+         %11 = OpConstant %2 4
+         %12 = OpConstant %2 5
+         %13 = OpConstant %2 6
+         %14 = OpConstant %2 7
+         %15 = OpConstant %2 8
+
+; Constant vectors
+         %16 = OpConstantComposite %3 %8 %9 %10 %11
+         %17 = OpConstantComposite %3 %12 %13 %14 %15
+
+; dot product function
+         %18 = OpFunction %2 None %7
+         %19 = OpFunctionParameter %4
+         %20 = OpFunctionParameter %4
+         %21 = OpLabel
+         %22 = OpLoad %3 %19
+         %23 = OpLoad %3 %20
+         %24 = OpCompositeExtract %2 %22 0
+         %25 = OpCompositeExtract %2 %23 0
+         %26 = OpFMul %2 %24 %25
+         %27 = OpCompositeExtract %2 %22 1
+         %28 = OpCompositeExtract %2 %23 1
+         %29 = OpFMul %2 %27 %28
+         %30 = OpCompositeExtract %2 %22 2
+         %31 = OpCompositeExtract %2 %23 2
+         %32 = OpFMul %2 %30 %31
+         %33 = OpCompositeExtract %2 %22 3
+         %34 = OpCompositeExtract %2 %23 3
+         %35 = OpFMul %2 %33 %34
+         %36 = OpFAdd %2 %26 %29
+         %37 = OpFAdd %2 %32 %36
+         %38 = OpFAdd %2 %35 %37
+               OpReturnValue %38
+               OpFunctionEnd
+
+; main function
+         %39 = OpFunction %5 None %6
+         %40 = OpLabel
+         %41 = OpVariable %4 Function
+         %42 = OpVariable %4 Function
+               OpStore %41 %16
+               OpStore %42 %17
+         %43 = OpFunctionCall %2 %18 %41 %42 ; dot product function call
+               OpBranch %44
+         %44 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  auto transformation = TransformationInlineFunction(43, {{22, 45},
+                                                          {23, 46},
+                                                          {24, 47},
+                                                          {25, 48},
+                                                          {26, 49},
+                                                          {27, 50},
+                                                          {28, 51},
+                                                          {29, 52},
+                                                          {30, 53},
+                                                          {31, 54},
+                                                          {32, 55},
+                                                          {33, 56},
+                                                          {34, 57},
+                                                          {35, 58},
+                                                          {36, 59},
+                                                          {37, 60},
+                                                          {38, 61}});
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %39 "main"
+
+; Types
+          %2 = OpTypeFloat 32
+          %3 = OpTypeVector %2 4
+          %4 = OpTypePointer Function %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFunction %2 %4 %4
+
+; Constant scalars
+          %8 = OpConstant %2 1
+          %9 = OpConstant %2 2
+         %10 = OpConstant %2 3
+         %11 = OpConstant %2 4
+         %12 = OpConstant %2 5
+         %13 = OpConstant %2 6
+         %14 = OpConstant %2 7
+         %15 = OpConstant %2 8
+
+; Constant vectors
+         %16 = OpConstantComposite %3 %8 %9 %10 %11
+         %17 = OpConstantComposite %3 %12 %13 %14 %15
+
+; dot product function
+         %18 = OpFunction %2 None %7
+         %19 = OpFunctionParameter %4
+         %20 = OpFunctionParameter %4
+         %21 = OpLabel
+         %22 = OpLoad %3 %19
+         %23 = OpLoad %3 %20
+         %24 = OpCompositeExtract %2 %22 0
+         %25 = OpCompositeExtract %2 %23 0
+         %26 = OpFMul %2 %24 %25
+         %27 = OpCompositeExtract %2 %22 1
+         %28 = OpCompositeExtract %2 %23 1
+         %29 = OpFMul %2 %27 %28
+         %30 = OpCompositeExtract %2 %22 2
+         %31 = OpCompositeExtract %2 %23 2
+         %32 = OpFMul %2 %30 %31
+         %33 = OpCompositeExtract %2 %22 3
+         %34 = OpCompositeExtract %2 %23 3
+         %35 = OpFMul %2 %33 %34
+         %36 = OpFAdd %2 %26 %29
+         %37 = OpFAdd %2 %32 %36
+         %38 = OpFAdd %2 %35 %37
+               OpReturnValue %38
+               OpFunctionEnd
+
+; main function
+         %39 = OpFunction %5 None %6
+         %40 = OpLabel
+         %41 = OpVariable %4 Function
+         %42 = OpVariable %4 Function
+               OpStore %41 %16
+               OpStore %42 %17
+         %45 = OpLoad %3 %41
+         %46 = OpLoad %3 %42
+         %47 = OpCompositeExtract %2 %45 0
+         %48 = OpCompositeExtract %2 %46 0
+         %49 = OpFMul %2 %47 %48
+         %50 = OpCompositeExtract %2 %45 1
+         %51 = OpCompositeExtract %2 %46 1
+         %52 = OpFMul %2 %50 %51
+         %53 = OpCompositeExtract %2 %45 2
+         %54 = OpCompositeExtract %2 %46 2
+         %55 = OpFMul %2 %53 %54
+         %56 = OpCompositeExtract %2 %45 3
+         %57 = OpCompositeExtract %2 %46 3
+         %58 = OpFMul %2 %56 %57
+         %59 = OpFAdd %2 %49 %52
+         %60 = OpFAdd %2 %55 %59
+         %61 = OpFAdd %2 %58 %60
+         %43 = OpCopyObject %2 %61
+               OpBranch %44
+         %44 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationInlineFunctionTest, ApplyToMultipleFunctions) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %15 "main"
+
+; Types
+          %2 = OpTypeInt 32 1
+          %3 = OpTypeBool
+          %4 = OpTypePointer Private %2
+          %5 = OpTypePointer Function %2
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFunction %2 %5
+          %9 = OpTypeFunction %2 %2
+
+; Constants
+         %10 = OpConstant %2 0
+         %11 = OpConstant %2 1
+         %12 = OpConstant %2 2
+         %13 = OpConstant %2 3
+
+; Global variable
+         %14 = OpVariable %4 Private
+
+; main function
+         %15 = OpFunction %6 None %7
+         %16 = OpLabel
+         %17 = OpVariable %5 Function
+         %18 = OpVariable %5 Function
+         %19 = OpVariable %5 Function
+               OpStore %17 %13
+         %20 = OpLoad %2 %17
+               OpStore %18 %20
+         %21 = OpFunctionCall %2 %36 %18
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpFunctionCall %2 %36 %18
+               OpStore %17 %21
+         %24 = OpLoad %2 %17
+         %25 = OpFunctionCall %2 %54 %24
+               OpBranch %26
+         %26 = OpLabel
+         %27 = OpFunctionCall %2 %54 %24
+         %28 = OpLoad %2 %17
+         %29 = OpIAdd %2 %28 %25
+               OpStore %17 %29
+         %30 = OpFunctionCall %6 %67
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpFunctionCall %6 %67
+         %33 = OpLoad %2 %14
+         %34 = OpLoad %2 %17
+         %35 = OpIAdd %2 %34 %33
+               OpStore %17 %35
+               OpReturn
+               OpFunctionEnd
+
+; Function %36
+         %36 = OpFunction %2 None %8
+         %37 = OpFunctionParameter %5
+         %38 = OpLabel
+         %39 = OpVariable %5 Function
+         %40 = OpVariable %5 Function
+               OpStore %39 %10
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %52 %49 None
+               OpBranch %42
+         %42 = OpLabel
+         %43 = OpLoad %2 %39
+         %44 = OpLoad %2 %37
+         %45 = OpSLessThan %3 %43 %44
+               OpBranchConditional %45 %46 %52
+         %46 = OpLabel
+         %47 = OpLoad %2 %40
+         %48 = OpIAdd %2 %47 %11
+               OpStore %40 %48
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %2 %39
+         %51 = OpIAdd %2 %50 %12
+               OpStore %39 %51
+               OpBranch %41
+         %52 = OpLabel
+         %53 = OpLoad %2 %40
+               OpReturnValue %53
+               OpFunctionEnd
+
+; Function %54
+         %54 = OpFunction %2 None %9
+         %55 = OpFunctionParameter %2
+         %56 = OpLabel
+         %57 = OpVariable %5 Function
+               OpStore %57 %10
+         %58 = OpSGreaterThan %3 %55 %10
+               OpSelectionMerge %62 None
+               OpBranchConditional %58 %64 %59
+         %59 = OpLabel
+         %60 = OpLoad %2 %57
+         %61 = OpISub %2 %60 %12
+               OpStore %57 %61
+               OpBranch %62
+         %62 = OpLabel
+         %63 = OpLoad %2 %57
+               OpReturnValue %63
+         %64 = OpLabel
+         %65 = OpLoad %2 %57
+         %66 = OpIAdd %2 %65 %11
+               OpStore %57 %66
+               OpBranch %62
+               OpFunctionEnd
+
+; Function %67
+         %67 = OpFunction %6 None %7
+         %68 = OpLabel
+               OpStore %14 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  auto transformation = TransformationInlineFunction(30, {});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+
+  // Tests a parameter included in the id map.
+  transformation = TransformationInlineFunction(25, {{55, 69},
+                                                     {56, 70},
+                                                     {57, 71},
+                                                     {58, 72},
+                                                     {59, 73},
+                                                     {60, 74},
+                                                     {61, 75},
+                                                     {62, 76},
+                                                     {63, 77},
+                                                     {64, 78},
+                                                     {65, 79},
+                                                     {66, 80}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the id of the returned value not included in the id map.
+  transformation = TransformationInlineFunction(25, {{56, 69},
+                                                     {57, 70},
+                                                     {58, 71},
+                                                     {59, 72},
+                                                     {60, 73},
+                                                     {61, 74},
+                                                     {62, 75},
+                                                     {64, 76},
+                                                     {65, 77},
+                                                     {66, 78}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationInlineFunction(25, {{57, 69},
+                                                     {58, 70},
+                                                     {59, 71},
+                                                     {60, 72},
+                                                     {61, 73},
+                                                     {62, 74},
+                                                     {63, 75},
+                                                     {64, 76},
+                                                     {65, 77},
+                                                     {66, 78}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+
+  transformation = TransformationInlineFunction(21, {{39, 79},
+                                                     {40, 80},
+                                                     {41, 81},
+                                                     {42, 82},
+                                                     {43, 83},
+                                                     {44, 84},
+                                                     {45, 85},
+                                                     {46, 86},
+                                                     {47, 87},
+                                                     {48, 88},
+                                                     {49, 89},
+                                                     {50, 90},
+                                                     {51, 91},
+                                                     {52, 92},
+                                                     {53, 93}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %15 "main"
+
+; Types
+          %2 = OpTypeInt 32 1
+          %3 = OpTypeBool
+          %4 = OpTypePointer Private %2
+          %5 = OpTypePointer Function %2
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFunction %2 %5
+          %9 = OpTypeFunction %2 %2
+
+; Constants
+         %10 = OpConstant %2 0
+         %11 = OpConstant %2 1
+         %12 = OpConstant %2 2
+         %13 = OpConstant %2 3
+
+; Global variable
+         %14 = OpVariable %4 Private
+
+; main function
+         %15 = OpFunction %6 None %7
+         %16 = OpLabel
+         %80 = OpVariable %5 Function
+         %79 = OpVariable %5 Function
+         %69 = OpVariable %5 Function
+         %17 = OpVariable %5 Function
+         %18 = OpVariable %5 Function
+         %19 = OpVariable %5 Function
+               OpStore %17 %13
+         %20 = OpLoad %2 %17
+               OpStore %18 %20
+               OpStore %79 %10
+               OpBranch %81
+         %81 = OpLabel
+               OpLoopMerge %92 %89 None
+               OpBranch %82
+         %82 = OpLabel
+         %83 = OpLoad %2 %79
+         %84 = OpLoad %2 %18
+         %85 = OpSLessThan %3 %83 %84
+               OpBranchConditional %85 %86 %92
+         %86 = OpLabel
+         %87 = OpLoad %2 %80
+         %88 = OpIAdd %2 %87 %11
+               OpStore %80 %88
+               OpBranch %89
+         %89 = OpLabel
+         %90 = OpLoad %2 %79
+         %91 = OpIAdd %2 %90 %12
+               OpStore %79 %91
+               OpBranch %81
+         %92 = OpLabel
+         %93 = OpLoad %2 %80
+         %21 = OpCopyObject %2 %93
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpFunctionCall %2 %36 %18
+               OpStore %17 %21
+         %24 = OpLoad %2 %17
+               OpStore %69 %10
+         %70 = OpSGreaterThan %3 %24 %10
+               OpSelectionMerge %74 None
+               OpBranchConditional %70 %76 %71
+         %71 = OpLabel
+         %72 = OpLoad %2 %69
+         %73 = OpISub %2 %72 %12
+               OpStore %69 %73
+               OpBranch %74
+         %74 = OpLabel
+         %75 = OpLoad %2 %69
+         %25 = OpCopyObject %2 %75
+               OpBranch %26
+         %76 = OpLabel
+         %77 = OpLoad %2 %69
+         %78 = OpIAdd %2 %77 %11
+               OpStore %69 %78
+               OpBranch %74
+         %26 = OpLabel
+         %27 = OpFunctionCall %2 %54 %24
+         %28 = OpLoad %2 %17
+         %29 = OpIAdd %2 %28 %25
+               OpStore %17 %29
+               OpStore %14 %12
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpFunctionCall %6 %67
+         %33 = OpLoad %2 %14
+         %34 = OpLoad %2 %17
+         %35 = OpIAdd %2 %34 %33
+               OpStore %17 %35
+               OpReturn
+               OpFunctionEnd
+
+; Function %36
+         %36 = OpFunction %2 None %8
+         %37 = OpFunctionParameter %5
+         %38 = OpLabel
+         %39 = OpVariable %5 Function
+         %40 = OpVariable %5 Function
+               OpStore %39 %10
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %52 %49 None
+               OpBranch %42
+         %42 = OpLabel
+         %43 = OpLoad %2 %39
+         %44 = OpLoad %2 %37
+         %45 = OpSLessThan %3 %43 %44
+               OpBranchConditional %45 %46 %52
+         %46 = OpLabel
+         %47 = OpLoad %2 %40
+         %48 = OpIAdd %2 %47 %11
+               OpStore %40 %48
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %2 %39
+         %51 = OpIAdd %2 %50 %12
+               OpStore %39 %51
+               OpBranch %41
+         %52 = OpLabel
+         %53 = OpLoad %2 %40
+               OpReturnValue %53
+               OpFunctionEnd
+
+; Function %54
+         %54 = OpFunction %2 None %9
+         %55 = OpFunctionParameter %2
+         %56 = OpLabel
+         %57 = OpVariable %5 Function
+               OpStore %57 %10
+         %58 = OpSGreaterThan %3 %55 %10
+               OpSelectionMerge %62 None
+               OpBranchConditional %58 %64 %59
+         %59 = OpLabel
+         %60 = OpLoad %2 %57
+         %61 = OpISub %2 %60 %12
+               OpStore %57 %61
+               OpBranch %62
+         %62 = OpLabel
+         %63 = OpLoad %2 %57
+               OpReturnValue %63
+         %64 = OpLabel
+         %65 = OpLoad %2 %57
+         %66 = OpIAdd %2 %65 %11
+               OpStore %57 %66
+               OpBranch %62
+               OpFunctionEnd
+
+; Function %67
+         %67 = OpFunction %6 None %7
+         %68 = OpLabel
+               OpStore %14 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools