Revert "[spirv-opt] refactor inlining pass (#3328)"

This reverts commit 233246bc9c5e0fef93f784a3f38899e35441519d.
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index bc07ff0..3c874a7 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -20,7 +20,6 @@
 #include <utility>
 
 #include "source/cfa.h"
-#include "source/opt/reflect.h"
 #include "source/util/make_unique.h"
 
 // Indices of operands in SPIR-V instructions
@@ -233,220 +232,6 @@
   });
 }
 
-void InlinePass::MoveInstsBeforeEntryBlock(
-    std::unordered_map<uint32_t, Instruction*>* preCallSB,
-    BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr,
-    UptrVectorIterator<BasicBlock> call_block_itr) {
-  for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
-       cii = call_block_itr->begin()) {
-    Instruction* inst = &*cii;
-    inst->RemoveFromList();
-    std::unique_ptr<Instruction> cp_inst(inst);
-    // Remember same-block ops for possible regeneration.
-    if (IsSameBlockOp(&*cp_inst)) {
-      auto* sb_inst_ptr = cp_inst.get();
-      (*preCallSB)[cp_inst->result_id()] = sb_inst_ptr;
-    }
-    new_blk_ptr->AddInstruction(std::move(cp_inst));
-  }
-}
-
-std::unique_ptr<BasicBlock> InlinePass::AddGuardBlock(
-    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
-    std::unordered_map<uint32_t, uint32_t>* callee2caller,
-    std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id) {
-  const auto guard_block_id = context()->TakeNextId();
-  if (guard_block_id == 0) {
-    return nullptr;
-  }
-  AddBranch(guard_block_id, &new_blk_ptr);
-  new_blocks->push_back(std::move(new_blk_ptr));
-  // Start the next block.
-  new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
-  // Reset the mapping of the callee's entry block to point to
-  // the guard block.  Do this so we can fix up phis later on to
-  // satisfy dominance.
-  (*callee2caller)[entry_blk_label_id] = guard_block_id;
-  return new_blk_ptr;
-}
-
-InstructionList::iterator InlinePass::AddStoresForVariableInitializers(
-    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-    std::unique_ptr<BasicBlock>* new_blk_ptr,
-    UptrVectorIterator<BasicBlock> callee_first_block_itr) {
-  auto callee_var_itr = callee_first_block_itr->begin();
-  while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) {
-    if (callee_var_itr->NumInOperands() == 2) {
-      assert(callee2caller.count(callee_var_itr->result_id()) &&
-             "Expected the variable to have already been mapped.");
-      uint32_t new_var_id = callee2caller.at(callee_var_itr->result_id());
-
-      // The initializer must be a constant or global value.  No mapped
-      // should be used.
-      uint32_t val_id = callee_var_itr->GetSingleWordInOperand(1);
-      AddStore(new_var_id, val_id, new_blk_ptr);
-    }
-    ++callee_var_itr;
-  }
-  return callee_var_itr;
-}
-
-bool InlinePass::InlineInstructionInBB(
-    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-    BasicBlock* new_blk_ptr, const Instruction* inst) {
-  // If we have return, it must be at the end of the callee. We will handle
-  // it at the end.
-  if (inst->opcode() == SpvOpReturnValue || inst->opcode() == SpvOpReturn)
-    return true;
-
-  // Copy callee instruction and remap all input Ids.
-  std::unique_ptr<Instruction> cp_inst(inst->Clone(context()));
-  cp_inst->ForEachInId([&callee2caller](uint32_t* iid) {
-    const auto mapItr = callee2caller.find(*iid);
-    if (mapItr != callee2caller.end()) {
-      *iid = mapItr->second;
-    }
-  });
-  // If result id is non-zero, remap it.
-  const uint32_t rid = cp_inst->result_id();
-  if (rid != 0) {
-    const auto mapItr = callee2caller.find(rid);
-    if (mapItr == callee2caller.end()) return false;
-    uint32_t nid = mapItr->second;
-    cp_inst->SetResultId(nid);
-    get_decoration_mgr()->CloneDecorations(rid, nid);
-  }
-  new_blk_ptr->AddInstruction(std::move(cp_inst));
-  return true;
-}
-
-std::unique_ptr<BasicBlock> InlinePass::InlineReturn(
-    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
-    std::unique_ptr<BasicBlock> new_blk_ptr, Function* calleeFn,
-    const Instruction* inst, uint32_t returnVarId) {
-  // Store return value to return variable.
-  if (inst->opcode() == SpvOpReturnValue) {
-    assert(returnVarId != 0);
-    uint32_t valId = inst->GetInOperand(kSpvReturnValueId).words[0];
-    const auto mapItr = callee2caller.find(valId);
-    if (mapItr != callee2caller.end()) {
-      valId = mapItr->second;
-    }
-    AddStore(returnVarId, valId, &new_blk_ptr);
-  }
-
-  uint32_t returnLabelId = 0;
-  for (auto callee_block_itr = calleeFn->begin();
-       callee_block_itr != calleeFn->end(); ++callee_block_itr) {
-    if (callee_block_itr->tail()->opcode() == SpvOpUnreachable ||
-        callee_block_itr->tail()->opcode() == SpvOpKill) {
-      returnLabelId = context()->TakeNextId();
-      break;
-    }
-  }
-  if (returnLabelId == 0) return new_blk_ptr;
-
-  if (inst->opcode() == SpvOpReturn || inst->opcode() == SpvOpReturnValue)
-    AddBranch(returnLabelId, &new_blk_ptr);
-  new_blocks->push_back(std::move(new_blk_ptr));
-  return MakeUnique<BasicBlock>(NewLabel(returnLabelId));
-}
-
-bool InlinePass::InlineEntryBlock(
-    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-    std::unique_ptr<BasicBlock>* new_blk_ptr,
-    UptrVectorIterator<BasicBlock> callee_first_block) {
-  auto callee_inst_itr = AddStoresForVariableInitializers(
-      callee2caller, new_blk_ptr, callee_first_block);
-
-  while (callee_inst_itr != callee_first_block->end()) {
-    if (!InlineInstructionInBB(callee2caller, new_blk_ptr->get(),
-                               &*callee_inst_itr)) {
-      return false;
-    }
-    ++callee_inst_itr;
-  }
-  return true;
-}
-
-std::unique_ptr<BasicBlock> InlinePass::InlineBasicBlocks(
-    std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
-    const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-    std::unique_ptr<BasicBlock> new_blk_ptr, Function* calleeFn) {
-  auto callee_block_itr = calleeFn->begin();
-  ++callee_block_itr;
-
-  while (callee_block_itr != calleeFn->end()) {
-    new_blocks->push_back(std::move(new_blk_ptr));
-    const auto mapItr =
-        callee2caller.find(callee_block_itr->GetLabelInst()->result_id());
-    if (mapItr == callee2caller.end()) return nullptr;
-    new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(mapItr->second));
-
-    auto tail_inst_itr = callee_block_itr->end();
-    for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr;
-         ++inst_itr) {
-      if (!InlineInstructionInBB(callee2caller, new_blk_ptr.get(),
-                                 &*inst_itr)) {
-        return nullptr;
-      }
-    }
-
-    ++callee_block_itr;
-  }
-  return new_blk_ptr;
-}
-
-bool InlinePass::MoveCallerInstsAfterFunctionCall(
-    std::unordered_map<uint32_t, Instruction*>* preCallSB,
-    std::unordered_map<uint32_t, uint32_t>* postCallSB,
-    std::unique_ptr<BasicBlock>* new_blk_ptr,
-    BasicBlock::iterator call_inst_itr, bool multiBlocks) {
-  // Copy remaining instructions from caller block.
-  for (Instruction* inst = call_inst_itr->NextNode(); inst;
-       inst = call_inst_itr->NextNode()) {
-    inst->RemoveFromList();
-    std::unique_ptr<Instruction> cp_inst(inst);
-    // If multiple blocks generated, regenerate any same-block
-    // instruction that has not been seen in this last block.
-    if (multiBlocks) {
-      if (!CloneSameBlockOps(&cp_inst, postCallSB, preCallSB, new_blk_ptr)) {
-        return false;
-      }
-
-      // Remember same-block ops in this block.
-      if (IsSameBlockOp(&*cp_inst)) {
-        const uint32_t rid = cp_inst->result_id();
-        (*postCallSB)[rid] = rid;
-      }
-    }
-    new_blk_ptr->get()->AddInstruction(std::move(cp_inst));
-  }
-
-  return true;
-}
-
-void InlinePass::MoveLoopMergeInstToFirstBlock(
-    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-  // Move the OpLoopMerge from the last block back to the first, where
-  // it belongs.
-  auto& first = new_blocks->front();
-  auto& last = new_blocks->back();
-  assert(first != last);
-
-  // Insert a modified copy of the loop merge into the first block.
-  auto loop_merge_itr = last->tail();
-  --loop_merge_itr;
-  assert(loop_merge_itr->opcode() == SpvOpLoopMerge);
-  std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context()));
-  first->tail().InsertBefore(std::move(cp_inst));
-
-  // Remove the loop merge from the last block.
-  loop_merge_itr->RemoveFromList();
-  delete &*loop_merge_itr;
-}
-
 bool InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<Instruction>>* new_vars,
@@ -465,19 +250,13 @@
   // valid.  These operations can fail.
   context()->InvalidateAnalyses(IRContext::kAnalysisDefUse);
 
-  // If the caller is a loop header and the callee has multiple blocks, then the
-  // normal inlining logic will place the OpLoopMerge in the last of several
-  // blocks in the loop.  Instead, it should be placed at the end of the first
-  // block.  We'll wait to move the OpLoopMerge until the end of the regular
-  // inlining logic, and only if necessary.
-  bool caller_is_loop_header = call_block_itr->GetLoopMergeInst() != nullptr;
-
-  // Single-trip loop continue block
-  std::unique_ptr<BasicBlock> single_trip_loop_cont_blk;
-
   Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
       kSpvFunctionCallFunctionId)];
 
+  // Check for multiple returns in the callee.
+  auto fi = early_return_funcs_.find(calleeFn->result_id());
+  const bool earlyReturn = fi != early_return_funcs_.end();
+
   // Map parameters to actual arguments.
   MapParams(calleeFn, call_inst_itr, &callee2caller);
 
@@ -487,31 +266,6 @@
     return false;
   }
 
-  // First block needs to use label of original block
-  // but map callee label in case of phi reference.
-  uint32_t entry_blk_label_id = calleeFn->begin()->GetLabelInst()->result_id();
-  callee2caller[entry_blk_label_id] = call_block_itr->id();
-  std::unique_ptr<BasicBlock> new_blk_ptr =
-      MakeUnique<BasicBlock>(NewLabel(call_block_itr->id()));
-
-  // Move instructions of original caller block up to call instruction.
-  MoveInstsBeforeEntryBlock(&preCallSB, new_blk_ptr.get(), call_inst_itr,
-                            call_block_itr);
-
-  if (caller_is_loop_header &&
-      (*(calleeFn->begin())).GetMergeInst() != nullptr) {
-    // We can't place both the caller's merge instruction and
-    // another merge instruction in the same block.  So split the
-    // calling block. Insert an unconditional branch to a new guard
-    // block.  Later, once we know the ID of the last block,  we
-    // will move the caller's OpLoopMerge from the last generated
-    // block into the first block. We also wait to avoid
-    // invalidating various iterators.
-    new_blk_ptr = AddGuardBlock(new_blocks, &callee2caller,
-                                std::move(new_blk_ptr), entry_blk_label_id);
-    if (new_blk_ptr == nullptr) return false;
-  }
-
   // Create return var if needed.
   const uint32_t calleeTypeId = calleeFn->type_id();
   uint32_t returnVarId = 0;
@@ -523,50 +277,341 @@
     }
   }
 
-  calleeFn->WhileEachInst([&callee2caller, this](const Instruction* cpi) {
-    // Create set of callee result ids. Used to detect forward references
+  // Create set of callee result ids. Used to detect forward references
+  std::unordered_set<uint32_t> callee_result_ids;
+  calleeFn->ForEachInst([&callee_result_ids](const Instruction* cpi) {
     const uint32_t rid = cpi->result_id();
-    if (rid != 0 && callee2caller.find(rid) == callee2caller.end()) {
-      const uint32_t nid = context()->TakeNextId();
-      if (nid == 0) return false;
-      callee2caller[rid] = nid;
-    }
-    return true;
+    if (rid != 0) callee_result_ids.insert(rid);
   });
 
-  // Inline the entry block of the callee function.
-  if (!InlineEntryBlock(callee2caller, &new_blk_ptr, calleeFn->begin())) {
+  // If the caller is a loop header and the callee has multiple blocks, then the
+  // normal inlining logic will place the OpLoopMerge in the last of several
+  // blocks in the loop.  Instead, it should be placed at the end of the first
+  // block.  We'll wait to move the OpLoopMerge until the end of the regular
+  // inlining logic, and only if necessary.
+  bool caller_is_loop_header = false;
+  if (call_block_itr->GetLoopMergeInst()) {
+    caller_is_loop_header = true;
+  }
+
+  bool callee_begins_with_structured_header =
+      (*(calleeFn->begin())).GetMergeInst() != nullptr;
+
+  // Clone and map callee code. Copy caller block code to beginning of
+  // first block and end of last block.
+  bool prevInstWasReturn = false;
+  uint32_t singleTripLoopHeaderId = 0;
+  uint32_t singleTripLoopContinueId = 0;
+  uint32_t returnLabelId = 0;
+  bool multiBlocks = false;
+  // new_blk_ptr is a new basic block in the caller.  New instructions are
+  // written to it.  It is created when we encounter the OpLabel
+  // of the first callee block.  It is appended to new_blocks only when
+  // it is complete.
+  std::unique_ptr<BasicBlock> new_blk_ptr;
+  bool successful = calleeFn->WhileEachInst(
+      [&new_blocks, &callee2caller, &call_block_itr, &call_inst_itr,
+       &new_blk_ptr, &prevInstWasReturn, &returnLabelId, &returnVarId,
+       caller_is_loop_header, callee_begins_with_structured_header,
+       &calleeTypeId, &multiBlocks, &postCallSB, &preCallSB, earlyReturn,
+       &singleTripLoopHeaderId, &singleTripLoopContinueId, &callee_result_ids,
+       this](const Instruction* cpi) {
+        switch (cpi->opcode()) {
+          case SpvOpFunction:
+          case SpvOpFunctionParameter:
+            // Already processed
+            break;
+          case SpvOpVariable:
+            if (cpi->NumInOperands() == 2) {
+              assert(callee2caller.count(cpi->result_id()) &&
+                     "Expected the variable to have already been mapped.");
+              uint32_t new_var_id = callee2caller.at(cpi->result_id());
+
+              // The initializer must be a constant or global value.  No mapped
+              // should be used.
+              uint32_t val_id = cpi->GetSingleWordInOperand(1);
+              AddStore(new_var_id, val_id, &new_blk_ptr);
+            }
+            break;
+          case SpvOpUnreachable:
+          case SpvOpKill: {
+            // Generate a return label so that we split the block with the
+            // function call. Copy the terminator into the new block.
+            if (returnLabelId == 0) {
+              returnLabelId = context()->TakeNextId();
+              if (returnLabelId == 0) {
+                return false;
+              }
+            }
+            std::unique_ptr<Instruction> terminator(
+                new Instruction(context(), cpi->opcode(), 0, 0, {}));
+            new_blk_ptr->AddInstruction(std::move(terminator));
+            break;
+          }
+          case SpvOpLabel: {
+            // If previous instruction was early return, insert branch
+            // instruction to return block.
+            if (prevInstWasReturn) {
+              if (returnLabelId == 0) {
+                returnLabelId = context()->TakeNextId();
+                if (returnLabelId == 0) {
+                  return false;
+                }
+              }
+              AddBranch(returnLabelId, &new_blk_ptr);
+              prevInstWasReturn = false;
+            }
+            // Finish current block (if it exists) and get label for next block.
+            uint32_t labelId;
+            bool firstBlock = false;
+            if (new_blk_ptr != nullptr) {
+              new_blocks->push_back(std::move(new_blk_ptr));
+              // If result id is already mapped, use it, otherwise get a new
+              // one.
+              const uint32_t rid = cpi->result_id();
+              const auto mapItr = callee2caller.find(rid);
+              labelId = (mapItr != callee2caller.end())
+                            ? mapItr->second
+                            : context()->TakeNextId();
+              if (labelId == 0) {
+                return false;
+              }
+            } else {
+              // First block needs to use label of original block
+              // but map callee label in case of phi reference.
+              labelId = call_block_itr->id();
+              callee2caller[cpi->result_id()] = labelId;
+              firstBlock = true;
+            }
+            // Create first/next block.
+            new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(labelId));
+            if (firstBlock) {
+              // Copy contents of original caller block up to call instruction.
+              for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
+                   cii = call_block_itr->begin()) {
+                Instruction* inst = &*cii;
+                inst->RemoveFromList();
+                std::unique_ptr<Instruction> cp_inst(inst);
+                // Remember same-block ops for possible regeneration.
+                if (IsSameBlockOp(&*cp_inst)) {
+                  auto* sb_inst_ptr = cp_inst.get();
+                  preCallSB[cp_inst->result_id()] = sb_inst_ptr;
+                }
+                new_blk_ptr->AddInstruction(std::move(cp_inst));
+              }
+              if (caller_is_loop_header &&
+                  callee_begins_with_structured_header) {
+                // We can't place both the caller's merge instruction and
+                // another merge instruction in the same block.  So split the
+                // calling block. Insert an unconditional branch to a new guard
+                // block.  Later, once we know the ID of the last block,  we
+                // will move the caller's OpLoopMerge from the last generated
+                // block into the first block. We also wait to avoid
+                // invalidating various iterators.
+                const auto guard_block_id = context()->TakeNextId();
+                if (guard_block_id == 0) {
+                  return false;
+                }
+                AddBranch(guard_block_id, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                // Start the next block.
+                new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(guard_block_id));
+                // Reset the mapping of the callee's entry block to point to
+                // the guard block.  Do this so we can fix up phis later on to
+                // satisfy dominance.
+                callee2caller[cpi->result_id()] = guard_block_id;
+              }
+              // If callee has early return, insert a header block for
+              // single-trip loop that will encompass callee code.  Start
+              // postheader block.
+              //
+              // Note: Consider the following combination:
+              //  - the caller is a single block loop
+              //  - the callee does not begin with a structure header
+              //  - the callee has multiple returns.
+              // We still need to split the caller block and insert a guard
+              // block. But we only need to do it once. We haven't done it yet,
+              // but the single-trip loop header will serve the same purpose.
+              if (earlyReturn) {
+                singleTripLoopHeaderId = context()->TakeNextId();
+                if (singleTripLoopHeaderId == 0) {
+                  return false;
+                }
+                AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr =
+                    MakeUnique<BasicBlock>(NewLabel(singleTripLoopHeaderId));
+                returnLabelId = context()->TakeNextId();
+                singleTripLoopContinueId = context()->TakeNextId();
+                if (returnLabelId == 0 || singleTripLoopContinueId == 0) {
+                  return false;
+                }
+                AddLoopMerge(returnLabelId, singleTripLoopContinueId,
+                             &new_blk_ptr);
+                uint32_t postHeaderId = context()->TakeNextId();
+                if (postHeaderId == 0) {
+                  return false;
+                }
+                AddBranch(postHeaderId, &new_blk_ptr);
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(postHeaderId));
+                multiBlocks = true;
+                // Reset the mapping of the callee's entry block to point to
+                // the post-header block.  Do this so we can fix up phis later
+                // on to satisfy dominance.
+                callee2caller[cpi->result_id()] = postHeaderId;
+              }
+            } else {
+              multiBlocks = true;
+            }
+          } break;
+          case SpvOpReturnValue: {
+            // Store return value to return variable.
+            assert(returnVarId != 0);
+            uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0];
+            const auto mapItr = callee2caller.find(valId);
+            if (mapItr != callee2caller.end()) {
+              valId = mapItr->second;
+            }
+            AddStore(returnVarId, valId, &new_blk_ptr);
+
+            // Remember we saw a return; if followed by a label, will need to
+            // insert branch.
+            prevInstWasReturn = true;
+          } break;
+          case SpvOpReturn: {
+            // Remember we saw a return; if followed by a label, will need to
+            // insert branch.
+            prevInstWasReturn = true;
+          } break;
+          case SpvOpFunctionEnd: {
+            // If there was an early return, we generated a return label id
+            // for it.  Now we have to generate the return block with that Id.
+            if (returnLabelId != 0) {
+              // If previous instruction was return, insert branch instruction
+              // to return block.
+              if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
+              if (earlyReturn) {
+                // If we generated a loop header for the single-trip loop
+                // to accommodate early returns, insert the continue
+                // target block now, with a false branch back to the loop
+                // header.
+                new_blocks->push_back(std::move(new_blk_ptr));
+                new_blk_ptr =
+                    MakeUnique<BasicBlock>(NewLabel(singleTripLoopContinueId));
+                uint32_t false_id = GetFalseId();
+                if (false_id == 0) {
+                  return false;
+                }
+                AddBranchCond(false_id, singleTripLoopHeaderId, returnLabelId,
+                              &new_blk_ptr);
+              }
+              // Generate the return block.
+              new_blocks->push_back(std::move(new_blk_ptr));
+              new_blk_ptr = MakeUnique<BasicBlock>(NewLabel(returnLabelId));
+              multiBlocks = true;
+            }
+            // Load return value into result id of call, if it exists.
+            if (returnVarId != 0) {
+              const uint32_t resId = call_inst_itr->result_id();
+              assert(resId != 0);
+              AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
+            }
+            // Copy remaining instructions from caller block.
+            for (Instruction* inst = call_inst_itr->NextNode(); inst;
+                 inst = call_inst_itr->NextNode()) {
+              inst->RemoveFromList();
+              std::unique_ptr<Instruction> cp_inst(inst);
+              // If multiple blocks generated, regenerate any same-block
+              // instruction that has not been seen in this last block.
+              if (multiBlocks) {
+                if (!CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB,
+                                       &new_blk_ptr)) {
+                  return false;
+                }
+
+                // Remember same-block ops in this block.
+                if (IsSameBlockOp(&*cp_inst)) {
+                  const uint32_t rid = cp_inst->result_id();
+                  postCallSB[rid] = rid;
+                }
+              }
+              new_blk_ptr->AddInstruction(std::move(cp_inst));
+            }
+            // Finalize inline code.
+            new_blocks->push_back(std::move(new_blk_ptr));
+          } break;
+          default: {
+            // Copy callee instruction and remap all input Ids.
+            std::unique_ptr<Instruction> cp_inst(cpi->Clone(context()));
+            bool succeeded = cp_inst->WhileEachInId(
+                [&callee2caller, &callee_result_ids, this](uint32_t* iid) {
+                  const auto mapItr = callee2caller.find(*iid);
+                  if (mapItr != callee2caller.end()) {
+                    *iid = mapItr->second;
+                  } else if (callee_result_ids.find(*iid) !=
+                             callee_result_ids.end()) {
+                    // Forward reference. Allocate a new id, map it,
+                    // use it and check for it when remapping result ids
+                    const uint32_t nid = context()->TakeNextId();
+                    if (nid == 0) {
+                      return false;
+                    }
+                    callee2caller[*iid] = nid;
+                    *iid = nid;
+                  }
+                  return true;
+                });
+            if (!succeeded) {
+              return false;
+            }
+            // If result id is non-zero, remap it. If already mapped, use mapped
+            // value, else use next id.
+            const uint32_t rid = cp_inst->result_id();
+            if (rid != 0) {
+              const auto mapItr = callee2caller.find(rid);
+              uint32_t nid;
+              if (mapItr != callee2caller.end()) {
+                nid = mapItr->second;
+              } else {
+                nid = context()->TakeNextId();
+                if (nid == 0) {
+                  return false;
+                }
+                callee2caller[rid] = nid;
+              }
+              cp_inst->SetResultId(nid);
+              get_decoration_mgr()->CloneDecorations(rid, nid);
+            }
+            new_blk_ptr->AddInstruction(std::move(cp_inst));
+          } break;
+        }
+        return true;
+      });
+
+  if (!successful) {
     return false;
   }
 
-  // Inline blocks of the callee function other than the entry block.
-  new_blk_ptr = InlineBasicBlocks(new_blocks, callee2caller,
-                                  std::move(new_blk_ptr), calleeFn);
-  if (new_blk_ptr == nullptr) return false;
+  if (caller_is_loop_header && (new_blocks->size() > 1)) {
+    // Move the OpLoopMerge from the last block back to the first, where
+    // it belongs.
+    auto& first = new_blocks->front();
+    auto& last = new_blocks->back();
+    assert(first != last);
 
-  new_blk_ptr =
-      InlineReturn(callee2caller, new_blocks, std::move(new_blk_ptr), calleeFn,
-                   &*(calleeFn->tail()->tail()), returnVarId);
+    // Insert a modified copy of the loop merge into the first block.
+    auto loop_merge_itr = last->tail();
+    --loop_merge_itr;
+    assert(loop_merge_itr->opcode() == SpvOpLoopMerge);
+    std::unique_ptr<Instruction> cp_inst(loop_merge_itr->Clone(context()));
+    first->tail().InsertBefore(std::move(cp_inst));
 
-  // Load return value into result id of call, if it exists.
-  if (returnVarId != 0) {
-    const uint32_t resId = call_inst_itr->result_id();
-    assert(resId != 0);
-    AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr);
+    // Remove the loop merge from the last block.
+    loop_merge_itr->RemoveFromList();
+    delete &*loop_merge_itr;
   }
 
-  // Move instructions of original caller block after call instruction.
-  if (!MoveCallerInstsAfterFunctionCall(&preCallSB, &postCallSB, &new_blk_ptr,
-                                        call_inst_itr,
-                                        calleeFn->begin() != calleeFn->end()))
-    return false;
-
-  // Finalize inline code.
-  new_blocks->push_back(std::move(new_blk_ptr));
-
-  if (caller_is_loop_header && (new_blocks->size() > 1))
-    MoveLoopMergeInstToFirstBlock(new_blocks);
-
   // Update block map given replacement blocks.
   for (auto& blk : *new_blocks) {
     id2block_[blk->id()] = &*blk;
@@ -579,21 +624,7 @@
   const uint32_t calleeFnId =
       inst->GetSingleWordOperand(kSpvFunctionCallFunctionId);
   const auto ci = inlinable_.find(calleeFnId);
-  if (ci == inlinable_.cend()) return false;
-
-  if (early_return_funcs_.find(calleeFnId) != early_return_funcs_.end()) {
-    // We rely on the merge-return pass to handle the early return case
-    // in advance.
-    std::string message =
-        "The function '" + id2function_[calleeFnId]->DefInst().PrettyPrint() +
-        "' could not be inlined because the return instruction "
-        "is not at the end of the function. This could be fixed by "
-        "running merge-return before inlining.";
-    consumer()(SPV_MSG_WARNING, "", {0, 0, 0}, message.c_str());
-    return false;
-  }
-
-  return true;
+  return ci != inlinable_.cend();
 }
 
 void InlinePass::UpdateSucceedingPhis(
@@ -614,6 +645,26 @@
       });
 }
 
+bool InlinePass::HasNoReturnInStructuredConstruct(Function* func) {
+  // If control not structured, do not do loop/return analysis
+  // TODO: Analyze returns in non-structured control flow
+  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
+    return false;
+  const auto structured_analysis = context()->GetStructuredCFGAnalysis();
+  // Search for returns in structured construct.
+  bool return_in_construct = false;
+  for (auto& blk : *func) {
+    auto terminal_ii = blk.cend();
+    --terminal_ii;
+    if (spvOpcodeIsReturn(terminal_ii->opcode()) &&
+        structured_analysis->ContainingConstruct(blk.id()) != 0) {
+      return_in_construct = true;
+      break;
+    }
+  }
+  return !return_in_construct;
+}
+
 bool InlinePass::HasNoReturnInLoop(Function* func) {
   // If control not structured, do not do loop/return analysis
   // TODO: Analyze returns in non-structured control flow
@@ -635,18 +686,10 @@
 }
 
 void InlinePass::AnalyzeReturns(Function* func) {
-  // Analyze functions without a return in loop.
   if (HasNoReturnInLoop(func)) {
     no_return_in_loop_.insert(func->result_id());
-  }
-  // Analyze functions with a return before its tail basic block.
-  for (auto& blk : *func) {
-    auto terminal_ii = blk.cend();
-    --terminal_ii;
-    if (spvOpcodeIsReturn(terminal_ii->opcode()) && &blk != func->tail()) {
+    if (!HasNoReturnInStructuredConstruct(func))
       early_return_funcs_.insert(func->result_id());
-      break;
-    }
   }
 }
 
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index 19fb26e..bc5f781 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -124,6 +124,10 @@
   // Return true if |inst| is a function call that can be inlined.
   bool IsInlinableFunctionCall(const Instruction* inst);
 
+  // Return true if |func| does not have a return that is
+  // nested in a structured if, switch or loop.
+  bool HasNoReturnInStructuredConstruct(Function* func);
+
   // Return true if |func| has no return in a loop. The current analysis
   // requires structured control flow, so return false if control flow not
   // structured ie. module is not a shader.
@@ -167,64 +171,6 @@
   // Set of functions that are originally called directly or indirectly from a
   // continue construct.
   std::unordered_set<uint32_t> funcs_called_from_continue_;
-
- private:
-  // Moves instructions of the caller function up to the call instruction
-  // to |new_blk_ptr|.
-  void MoveInstsBeforeEntryBlock(
-      std::unordered_map<uint32_t, Instruction*>* preCallSB,
-      BasicBlock* new_blk_ptr, BasicBlock::iterator call_inst_itr,
-      UptrVectorIterator<BasicBlock> call_block_itr);
-
-  // Returns a new guard block after adding a branch to the end of
-  // |new_blocks|.
-  std::unique_ptr<BasicBlock> AddGuardBlock(
-      std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
-      std::unordered_map<uint32_t, uint32_t>* callee2caller,
-      std::unique_ptr<BasicBlock> new_blk_ptr, uint32_t entry_blk_label_id);
-
-  // Add store instructions for initializers of variables.
-  InstructionList::iterator AddStoresForVariableInitializers(
-      const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-      std::unique_ptr<BasicBlock>* new_blk_ptr,
-      UptrVectorIterator<BasicBlock> callee_block_itr);
-
-  // Inlines a single instruction of the callee function.
-  bool InlineInstructionInBB(
-      const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-      BasicBlock* new_blk_ptr, const Instruction* inst);
-
-  // Inlines the return instruction of the callee function.
-  std::unique_ptr<BasicBlock> InlineReturn(
-      const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-      std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
-      std::unique_ptr<BasicBlock> new_blk_ptr, Function* calleeFn,
-      const Instruction* inst, uint32_t returnVarId);
-
-  // Inlines the entry block of the callee function.
-  bool InlineEntryBlock(
-      const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-      std::unique_ptr<BasicBlock>* new_blk_ptr,
-      UptrVectorIterator<BasicBlock> callee_first_block);
-
-  // Inlines basic blocks of the callee function other than the entry basic
-  // block.
-  std::unique_ptr<BasicBlock> InlineBasicBlocks(
-      std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
-      const std::unordered_map<uint32_t, uint32_t>& callee2caller,
-      std::unique_ptr<BasicBlock> new_blk_ptr, Function* calleeFn);
-
-  // Moves instructions of the caller function after the call instruction
-  // to |new_blk_ptr|.
-  bool MoveCallerInstsAfterFunctionCall(
-      std::unordered_map<uint32_t, Instruction*>* preCallSB,
-      std::unordered_map<uint32_t, uint32_t>* postCallSB,
-      std::unique_ptr<BasicBlock>* new_blk_ptr,
-      BasicBlock::iterator call_inst_itr, bool multiBlocks);
-
-  // Move the OpLoopMerge from the last block back to the first.
-  void MoveLoopMergeInstToFirstBlock(
-      std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 };
 
 }  // namespace opt
diff --git a/test/opt/inline_opaque_test.cpp b/test/opt/inline_opaque_test.cpp
index b8d2dfa..d10913a 100644
--- a/test/opt/inline_opaque_test.cpp
+++ b/test/opt/inline_opaque_test.cpp
@@ -102,12 +102,12 @@
 OpStore %32 %31
 %33 = OpLoad %S_t %s0
 OpStore %param %33
-%42 = OpAccessChain %_ptr_Function_18 %param %int_2
-%43 = OpLoad %18 %42
-%44 = OpAccessChain %_ptr_Function_v2float %param %int_0
-%45 = OpLoad %v2float %44
-%46 = OpImageSampleImplicitLod %v4float %43 %45
-OpStore %outColor %46
+%41 = OpAccessChain %_ptr_Function_18 %param %int_2
+%42 = OpLoad %18 %41
+%43 = OpAccessChain %_ptr_Function_v2float %param %int_0
+%44 = OpLoad %v2float %43
+%45 = OpImageSampleImplicitLod %v4float %42 %44
+OpStore %outColor %45
 OpReturn
 OpFunctionEnd
 )";
@@ -191,10 +191,10 @@
 %34 = OpVariable %_ptr_Function_20 Function
 %35 = OpVariable %_ptr_Function_20 Function
 %25 = OpVariable %_ptr_Function_20 Function
-%37 = OpLoad %20 %sampler16
-OpStore %34 %37
-%38 = OpLoad %20 %34
-OpStore %35 %38
+%36 = OpLoad %20 %sampler16
+OpStore %34 %36
+%37 = OpLoad %20 %34
+OpStore %35 %37
 %26 = OpLoad %20 %35
 OpStore %25 %26
 %27 = OpLoad %20 %25
@@ -301,12 +301,12 @@
 OpStore %33 %32
 %34 = OpLoad %S_t %s0
 OpStore %param %34
-%45 = OpAccessChain %_ptr_Function_19 %param %int_2
-%46 = OpLoad %19 %45
-%47 = OpAccessChain %_ptr_Function_v2float %param %int_0
-%48 = OpLoad %v2float %47
-%49 = OpImageSampleImplicitLod %v4float %46 %48
-OpStore %outColor %49
+%44 = OpAccessChain %_ptr_Function_19 %param %int_2
+%45 = OpLoad %19 %44
+%46 = OpAccessChain %_ptr_Function_v2float %param %int_0
+%47 = OpLoad %v2float %46
+%48 = OpImageSampleImplicitLod %v4float %45 %47
+OpStore %outColor %48
 OpReturn
 OpFunctionEnd
 )";
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index 76573a6..f44c04a 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -13,7 +13,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <iostream>
 #include <memory>
 #include <string>
 #include <vector>
@@ -116,12 +115,12 @@
       "%param = OpVariable %_ptr_Function_v4float Function",
          "%22 = OpLoad %v4float %BaseColor",
                "OpStore %param %22",
-         "%34 = OpAccessChain %_ptr_Function_float %param %uint_0",
-         "%35 = OpLoad %float %34",
-         "%36 = OpAccessChain %_ptr_Function_float %param %uint_1",
-         "%37 = OpLoad %float %36",
-         "%38 = OpFAdd %float %35 %37",
-               "OpStore %32 %38",
+         "%33 = OpAccessChain %_ptr_Function_float %param %uint_0",
+         "%34 = OpLoad %float %33",
+         "%35 = OpAccessChain %_ptr_Function_float %param %uint_1",
+         "%36 = OpLoad %float %35",
+         "%37 = OpFAdd %float %34 %36",
+               "OpStore %32 %37",
          "%23 = OpLoad %float %32",
          "%24 = OpCompositeConstruct %v4float %23 %23 %23 %23",
                "OpStore %color %24",
@@ -249,7 +248,7 @@
       // clang-format off
        "%main = OpFunction %void None %15",
          "%28 = OpLabel",
-         "%58 = OpVariable %_ptr_Function_float Function",
+         "%57 = OpVariable %_ptr_Function_float Function",
          "%46 = OpVariable %_ptr_Function_float Function",
          "%47 = OpVariable %_ptr_Function_float Function",
          "%48 = OpVariable %_ptr_Function_float Function",
@@ -257,21 +256,21 @@
     "%param_1 = OpVariable %_ptr_Function_v4float Function",
          "%29 = OpLoad %v4float %BaseColor",
                "OpStore %param_1 %29",
-         "%50 = OpAccessChain %_ptr_Function_float %param_1 %uint_0",
-         "%51 = OpLoad %float %50",
-         "%52 = OpAccessChain %_ptr_Function_float %param_1 %uint_1",
-         "%53 = OpLoad %float %52",
-         "%54 = OpFAdd %float %51 %53",
-               "OpStore %46 %54",
-         "%55 = OpAccessChain %_ptr_Function_float %param_1 %uint_2",
-         "%56 = OpLoad %float %55",
-               "OpStore %47 %56",
-         "%60 = OpLoad %float %46",
-         "%61 = OpLoad %float %47",
-         "%62 = OpFMul %float %60 %61",
-               "OpStore %58 %62",
-         "%57 = OpLoad %float %58",
-               "OpStore %48 %57",
+         "%49 = OpAccessChain %_ptr_Function_float %param_1 %uint_0",
+         "%50 = OpLoad %float %49",
+         "%51 = OpAccessChain %_ptr_Function_float %param_1 %uint_1",
+         "%52 = OpLoad %float %51",
+         "%53 = OpFAdd %float %50 %52",
+               "OpStore %46 %53",
+         "%54 = OpAccessChain %_ptr_Function_float %param_1 %uint_2",
+         "%55 = OpLoad %float %54",
+               "OpStore %47 %55",
+         "%58 = OpLoad %float %46",
+         "%59 = OpLoad %float %47",
+         "%60 = OpFMul %float %58 %59",
+               "OpStore %57 %60",
+         "%56 = OpLoad %float %57",
+               "OpStore %48 %56",
          "%30 = OpLoad %float %48",
          "%31 = OpCompositeConstruct %v4float %30 %30 %30 %30",
                "OpStore %color %31",
@@ -391,13 +390,13 @@
                "OpStore %b %24",
          "%25 = OpLoad %v4float %b",
                "OpStore %param %25",
-         "%40 = OpAccessChain %_ptr_Function_float %param %uint_0",
-         "%41 = OpLoad %float %40",
-         "%42 = OpAccessChain %_ptr_Function_float %param %uint_1",
-         "%43 = OpLoad %float %42",
-         "%44 = OpFAdd %float %41 %43",
-         "%45 = OpAccessChain %_ptr_Function_float %param %uint_2",
-               "OpStore %45 %44",
+         "%39 = OpAccessChain %_ptr_Function_float %param %uint_0",
+         "%40 = OpLoad %float %39",
+         "%41 = OpAccessChain %_ptr_Function_float %param %uint_1",
+         "%42 = OpLoad %float %41",
+         "%43 = OpFAdd %float %40 %42",
+         "%44 = OpAccessChain %_ptr_Function_float %param %uint_2",
+               "OpStore %44 %43",
          "%27 = OpLoad %v4float %param",
                "OpStore %b %27",
          "%28 = OpAccessChain %_ptr_Function_float %b %uint_2",
@@ -522,21 +521,21 @@
       "%param = OpVariable %_ptr_Function_v4float Function",
          "%24 = OpLoad %v4float %BaseColor",
                "OpStore %param %24",
-         "%41 = OpAccessChain %_ptr_Function_float %param %uint_0",
-         "%42 = OpLoad %float %41",
-               "OpStore %38 %42",
-         "%43 = OpLoad %float %38",
-         "%44 = OpFOrdLessThan %bool %43 %float_0",
-               "OpSelectionMerge %48 None",
-               "OpBranchConditional %44 %45 %48",
+         "%40 = OpAccessChain %_ptr_Function_float %param %uint_0",
+         "%41 = OpLoad %float %40",
+               "OpStore %38 %41",
+         "%42 = OpLoad %float %38",
+         "%43 = OpFOrdLessThan %bool %42 %float_0",
+               "OpSelectionMerge %44 None",
+               "OpBranchConditional %43 %45 %44",
          "%45 = OpLabel",
          "%46 = OpLoad %float %38",
          "%47 = OpFNegate %float %46",
                "OpStore %38 %47",
-               "OpBranch %48",
-         "%48 = OpLabel",
-         "%49 = OpLoad %float %38",
-               "OpStore %39 %49",
+               "OpBranch %44",
+         "%44 = OpLabel",
+         "%48 = OpLoad %float %38",
+               "OpStore %39 %48",
          "%25 = OpLoad %float %39",
          "%26 = OpCompositeConstruct %v4float %25 %25 %25 %25",
                "OpStore %color %26",
@@ -676,8 +675,8 @@
       // clang-format off
        "%main = OpFunction %void None %12",
          "%27 = OpLabel",
+         "%62 = OpVariable %_ptr_Function_float Function",
          "%63 = OpVariable %_ptr_Function_float Function",
-         "%64 = OpVariable %_ptr_Function_float Function",
          "%52 = OpVariable %_ptr_Function_float Function",
          "%53 = OpVariable %_ptr_Function_float Function",
       "%color = OpVariable %_ptr_Function_v4float Function",
@@ -688,20 +687,20 @@
          "%29 = OpAccessChain %_ptr_Function_float %color %uint_0",
          "%30 = OpLoad %float %29",
                "OpStore %param %30",
-         "%55 = OpLoad %float %param",
-               "OpStore %52 %55",
-         "%56 = OpLoad %float %52",
-         "%57 = OpFOrdLessThan %bool %56 %float_0",
-               "OpSelectionMerge %61 None",
-               "OpBranchConditional %57 %58 %61",
+         "%54 = OpLoad %float %param",
+               "OpStore %52 %54",
+         "%55 = OpLoad %float %52",
+         "%56 = OpFOrdLessThan %bool %55 %float_0",
+               "OpSelectionMerge %57 None",
+               "OpBranchConditional %56 %58 %57",
          "%58 = OpLabel",
          "%59 = OpLoad %float %52",
          "%60 = OpFNegate %float %59",
                "OpStore %52 %60",
-               "OpBranch %61",
-         "%61 = OpLabel",
-         "%62 = OpLoad %float %52",
-               "OpStore %53 %62",
+               "OpBranch %57",
+         "%57 = OpLabel",
+         "%61 = OpLoad %float %52",
+               "OpStore %53 %61",
          "%31 = OpLoad %float %53",
          "%32 = OpFOrdGreaterThan %bool %31 %float_2",
                "OpSelectionMerge %33 None",
@@ -710,25 +709,25 @@
          "%35 = OpAccessChain %_ptr_Function_float %color %uint_1",
          "%36 = OpLoad %float %35",
                "OpStore %param_0 %36",
-         "%66 = OpLoad %float %param_0",
-               "OpStore %63 %66",
-         "%67 = OpLoad %float %63",
-         "%68 = OpFOrdLessThan %bool %67 %float_0",
-               "OpSelectionMerge %72 None",
-               "OpBranchConditional %68 %69 %72",
-         "%69 = OpLabel",
-         "%70 = OpLoad %float %63",
-         "%71 = OpFNegate %float %70",
+         "%64 = OpLoad %float %param_0",
+               "OpStore %62 %64",
+         "%65 = OpLoad %float %62",
+         "%66 = OpFOrdLessThan %bool %65 %float_0",
+               "OpSelectionMerge %67 None",
+               "OpBranchConditional %66 %68 %67",
+         "%68 = OpLabel",
+         "%69 = OpLoad %float %62",
+         "%70 = OpFNegate %float %69",
+               "OpStore %62 %70",
+               "OpBranch %67",
+         "%67 = OpLabel",
+         "%71 = OpLoad %float %62",
                "OpStore %63 %71",
-               "OpBranch %72",
-         "%72 = OpLabel",
-         "%73 = OpLoad %float %63",
-               "OpStore %64 %73",
-         "%37 = OpLoad %float %64",
+         "%37 = OpLoad %float %63",
          "%38 = OpFOrdGreaterThan %bool %37 %float_2",
                "OpBranch %33",
          "%33 = OpLabel",
-         "%39 = OpPhi %bool %32 %61 %38 %72",
+         "%39 = OpPhi %bool %32 %57 %38 %67",
                "OpSelectionMerge %40 None",
                "OpBranchConditional %39 %41 %40",
          "%41 = OpLabel",
@@ -903,28 +902,28 @@
                "OpStore %color1 %42",
          "%43 = OpLoad %v4float %BaseColor",
                "OpStore %param %43",
-         "%69 = OpAccessChain %_ptr_Function_float %param %uint_0",
-         "%70 = OpLoad %float %69",
-               "OpStore %66 %70",
-         "%71 = OpLoad %float %66",
-         "%72 = OpFOrdLessThan %bool %71 %float_0",
-               "OpSelectionMerge %76 None",
-               "OpBranchConditional %72 %73 %76",
+         "%68 = OpAccessChain %_ptr_Function_float %param %uint_0",
+         "%69 = OpLoad %float %68",
+               "OpStore %66 %69",
+         "%70 = OpLoad %float %66",
+         "%71 = OpFOrdLessThan %bool %70 %float_0",
+               "OpSelectionMerge %72 None",
+               "OpBranchConditional %71 %73 %72",
          "%73 = OpLabel",
          "%74 = OpLoad %float %66",
          "%75 = OpFNegate %float %74",
                "OpStore %66 %75",
-               "OpBranch %76",
-         "%76 = OpLabel",
-         "%77 = OpLoad %float %66",
-               "OpStore %67 %77",
+               "OpBranch %72",
+         "%72 = OpLabel",
+         "%76 = OpLoad %float %66",
+               "OpStore %67 %76",
          "%44 = OpLoad %float %67",
          "%45 = OpCompositeConstruct %v4float %44 %44 %44 %44",
                "OpStore %color2 %45",
          "%46 = OpLoad %25 %t2D",
          "%47 = OpLoad %27 %samp",
-         "%78 = OpSampledImage %29 %39 %40",
-         "%48 = OpImageSampleImplicitLod %v4float %78 %35",
+         "%77 = OpSampledImage %29 %39 %40",
+         "%48 = OpImageSampleImplicitLod %v4float %77 %35",
                "OpStore %color3 %48",
          "%49 = OpLoad %v4float %color1",
          "%50 = OpLoad %v4float %color2",
@@ -1109,27 +1108,27 @@
                "OpStore %color1 %43",
          "%46 = OpLoad %v4float %BaseColor",
                "OpStore %param %46",
-         "%71 = OpAccessChain %_ptr_Function_float %param %uint_0",
-         "%72 = OpLoad %float %71",
-               "OpStore %68 %72",
-         "%73 = OpLoad %float %68",
-         "%74 = OpFOrdLessThan %bool %73 %float_0",
-               "OpSelectionMerge %78 None",
-               "OpBranchConditional %74 %75 %78",
+         "%70 = OpAccessChain %_ptr_Function_float %param %uint_0",
+         "%71 = OpLoad %float %70",
+               "OpStore %68 %71",
+         "%72 = OpLoad %float %68",
+         "%73 = OpFOrdLessThan %bool %72 %float_0",
+               "OpSelectionMerge %74 None",
+               "OpBranchConditional %73 %75 %74",
          "%75 = OpLabel",
          "%76 = OpLoad %float %68",
          "%77 = OpFNegate %float %76",
                "OpStore %68 %77",
-               "OpBranch %78",
-         "%78 = OpLabel",
-         "%79 = OpLoad %float %68",
-               "OpStore %69 %79",
+               "OpBranch %74",
+         "%74 = OpLabel",
+         "%78 = OpLoad %float %68",
+               "OpStore %69 %78",
          "%47 = OpLoad %float %69",
          "%48 = OpCompositeConstruct %v4float %47 %47 %47 %47",
                "OpStore %color2 %48",
-         "%80 = OpSampledImage %30 %40 %41",
-         "%81 = OpImage %26 %80",
-         "%49 = OpSampledImage %30 %81 %45",
+         "%79 = OpSampledImage %30 %40 %41",
+         "%80 = OpImage %26 %79",
+         "%49 = OpSampledImage %30 %80 %45",
          "%50 = OpImageSampleImplicitLod %v4float %49 %36",
                "OpStore %color3 %50",
          "%51 = OpLoad %v4float %color1",
@@ -1315,28 +1314,28 @@
                "OpStore %color1 %43",
          "%47 = OpLoad %v4float %BaseColor",
                "OpStore %param %47",
-         "%71 = OpAccessChain %_ptr_Function_float %param %uint_0",
-         "%72 = OpLoad %float %71",
-               "OpStore %68 %72",
-         "%73 = OpLoad %float %68",
-         "%74 = OpFOrdLessThan %bool %73 %float_0",
-               "OpSelectionMerge %78 None",
-               "OpBranchConditional %74 %75 %78",
+         "%70 = OpAccessChain %_ptr_Function_float %param %uint_0",
+         "%71 = OpLoad %float %70",
+               "OpStore %68 %71",
+         "%72 = OpLoad %float %68",
+         "%73 = OpFOrdLessThan %bool %72 %float_0",
+               "OpSelectionMerge %74 None",
+               "OpBranchConditional %73 %75 %74",
          "%75 = OpLabel",
          "%76 = OpLoad %float %68",
          "%77 = OpFNegate %float %76",
                "OpStore %68 %77",
-               "OpBranch %78",
-         "%78 = OpLabel",
-         "%79 = OpLoad %float %68",
-               "OpStore %69 %79",
+               "OpBranch %74",
+         "%74 = OpLabel",
+         "%78 = OpLoad %float %68",
+               "OpStore %69 %78",
          "%48 = OpLoad %float %69",
          "%49 = OpCompositeConstruct %v4float %48 %48 %48 %48",
                "OpStore %color2 %49",
-         "%80 = OpSampledImage %30 %40 %41",
-         "%81 = OpImage %26 %80",
-         "%82 = OpSampledImage %30 %81 %45",
-         "%50 = OpImageSampleImplicitLod %v4float %82 %36",
+         "%79 = OpSampledImage %30 %40 %41",
+         "%80 = OpImage %26 %79",
+         "%81 = OpSampledImage %30 %80 %45",
+         "%50 = OpImageSampleImplicitLod %v4float %81 %36",
                "OpStore %color3 %50",
          "%51 = OpLoad %v4float %color1",
          "%52 = OpLoad %v4float %color2",
@@ -1356,6 +1355,292 @@
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
+TEST_F(InlineTest, EarlyReturnFunctionInlined) {
+  // #version 140
+  //
+  // in vec4 BaseColor;
+  //
+  // float foo(vec4 bar)
+  // {
+  //     if (bar.x < 0.0)
+  //         return 0.0;
+  //     return bar.x;
+  // }
+  //
+  // void main()
+  // {
+  //     vec4 color = vec4(foo(BaseColor));
+  //     gl_FragColor = color;
+  // }
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string nonEntryFuncs =
+      R"(%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%27 = OpLabel
+%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%29 = OpLoad %float %28
+%30 = OpFOrdLessThan %bool %29 %float_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %32 %31
+%32 = OpLabel
+OpReturnValue %float_0
+%31 = OpLabel
+%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%34 = OpLoad %float %33
+OpReturnValue %34
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %10
+%22 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+%24 = OpFunctionCall %float %foo_vf4_ %param
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%false = OpConstantFalse %bool
+%main = OpFunction %void None %10
+%22 = OpLabel
+%35 = OpVariable %_ptr_Function_float Function
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+OpBranch %36
+%36 = OpLabel
+OpLoopMerge %37 %38 None
+OpBranch %39
+%39 = OpLabel
+%40 = OpAccessChain %_ptr_Function_float %param %uint_0
+%41 = OpLoad %float %40
+%42 = OpFOrdLessThan %bool %41 %float_0
+OpSelectionMerge %43 None
+OpBranchConditional %42 %44 %43
+%44 = OpLabel
+OpStore %35 %float_0
+OpBranch %37
+%43 = OpLabel
+%45 = OpAccessChain %_ptr_Function_float %param %uint_0
+%46 = OpLoad %float %45
+OpStore %35 %46
+OpBranch %37
+%38 = OpLabel
+OpBranchConditional %false %36 %37
+%37 = OpLabel
+%24 = OpLoad %float %35
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + before + nonEntryFuncs,
+                                              predefs + after + nonEntryFuncs,
+                                              false, true);
+}
+
+TEST_F(InlineTest, EarlyReturnNotAppearingLastInFunctionInlined) {
+  // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/755
+  //
+  // Original example is derived from:
+  //
+  // #version 450
+  //
+  // float foo() {
+  //     if (true) {
+  //     }
+  // }
+  //
+  // void main() { foo(); }
+  //
+  // But the order of basic blocks in foo is changed so that the return
+  // block is listed second-last.  There is only one return in the callee
+  // but it does not appear last.
+
+  const std::string predefs =
+      R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_ "foo("
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+)";
+
+  const std::string nonEntryFuncs =
+      R"(%foo_ = OpFunction %void None %4
+%7 = OpLabel
+OpSelectionMerge %8 None
+OpBranchConditional %true %9 %8
+%8 = OpLabel
+OpReturn
+%9 = OpLabel
+OpBranch %8
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %4
+%10 = OpLabel
+%11 = OpFunctionCall %void %foo_
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %4
+%10 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %12
+%12 = OpLabel
+OpBranch %14
+%13 = OpLabel
+OpBranch %12
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+                                              predefs + nonEntryFuncs + after,
+                                              false, true);
+}
+
+TEST_F(InlineTest, ForwardReferencesInPhiInlined) {
+  // The basic structure of the test case is like this:
+  //
+  // int foo() {
+  //   int result = 1;
+  //   if (true) {
+  //      result = 1;
+  //   }
+  //   return result;
+  // }
+  //
+  // void main() {
+  //  int x = foo();
+  // }
+  //
+  // but with modifications: Using Phi instead of load/store, and the
+  // return block in foo appears before the "then" block.
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_ "foo("
+OpName %x "x"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%8 = OpTypeFunction %int
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int_0 = OpConstant %int 0
+%_ptr_Function_int = OpTypePointer Function %int
+)";
+
+  const std::string nonEntryFuncs =
+      R"(%foo_ = OpFunction %int None %8
+%13 = OpLabel
+%14 = OpCopyObject %int %int_0
+OpSelectionMerge %15 None
+OpBranchConditional %true %16 %15
+%15 = OpLabel
+%17 = OpPhi %int %14 %13 %18 %16
+OpReturnValue %17
+%16 = OpLabel
+%18 = OpCopyObject %int %int_0
+OpBranch %15
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %6
+%19 = OpLabel
+%x = OpVariable %_ptr_Function_int Function
+%20 = OpFunctionCall %int %foo_
+OpStore %x %20
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %6
+%19 = OpLabel
+%21 = OpVariable %_ptr_Function_int Function
+%x = OpVariable %_ptr_Function_int Function
+%22 = OpCopyObject %int %int_0
+OpSelectionMerge %23 None
+OpBranchConditional %true %24 %23
+%23 = OpLabel
+%26 = OpPhi %int %22 %19 %25 %24
+OpStore %21 %26
+OpBranch %27
+%24 = OpLabel
+%25 = OpCopyObject %int %int_0
+OpBranch %23
+%27 = OpLabel
+%20 = OpLoad %int %21
+OpStore %x %20
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+                                              predefs + nonEntryFuncs + after,
+                                              false, true);
+}
+
 TEST_F(InlineTest, EarlyReturnInLoopIsNotInlined) {
   // #version 140
   //
@@ -1535,8 +1820,8 @@
 OpBranch %10
 %10 = OpLabel
 OpLoopMerge %12 %10 None
-OpBranch %14
-%14 = OpLabel
+OpBranch %13
+%13 = OpLabel
 OpBranchConditional %true %10 %12
 %12 = OpLabel
 OpReturn
@@ -1605,11 +1890,11 @@
 OpBranch %18
 %18 = OpLabel
 %19 = OpCopyObject %int %int_3
-%26 = OpCopyObject %int %int_1
+%25 = OpCopyObject %int %int_1
 OpLoopMerge %22 %23 None
-OpBranch %27
-%27 = OpLabel
-%28 = OpCopyObject %int %int_2
+OpBranch %26
+%26 = OpLabel
+%27 = OpCopyObject %int %int_2
 %21 = OpCopyObject %int %int_4
 OpBranchConditional %true %23 %22
 %23 = OpLabel
@@ -1698,11 +1983,11 @@
 OpLoopMerge %16 %13 None
 OpBranch %17
 %17 = OpLabel
-%19 = OpCopyObject %bool %true
-OpSelectionMerge %20 None
-OpBranchConditional %true %20 %20
-%20 = OpLabel
-%21 = OpPhi %bool %19 %17
+%18 = OpCopyObject %bool %true
+OpSelectionMerge %19 None
+OpBranchConditional %true %19 %19
+%19 = OpLabel
+%20 = OpPhi %bool %18 %17
 OpBranchConditional %true %13 %16
 %16 = OpLabel
 OpReturn
@@ -1775,11 +2060,11 @@
 OpLoopMerge %22 %23 None
 OpBranch %25
 %25 = OpLabel
-%27 = OpCopyObject %int %int_1
-OpSelectionMerge %28 None
-OpBranchConditional %true %28 %28
-%28 = OpLabel
-%29 = OpCopyObject %int %int_2
+%26 = OpCopyObject %int %int_1
+OpSelectionMerge %27 None
+OpBranchConditional %true %27 %27
+%27 = OpLabel
+%28 = OpCopyObject %int %int_2
 %21 = OpCopyObject %int %int_4
 OpBranchConditional %true %23 %22
 %23 = OpLabel
@@ -1795,6 +2080,165 @@
                                               false, true);
 }
 
+TEST_F(
+    InlineTest,
+    SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMergeAndMultiReturns) {
+  // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge
+  // except that in addition to starting with a selection header, the
+  // callee also has multi returns.
+  //
+  // So now we have to accommodate:
+  // - The caller's OpLoopMerge (which must move to the first block)
+  // - The single-trip loop to wrap the multi returns, and
+  // - The callee's selection merge in its first block.
+  // Each of these must go into their own blocks.
+
+  const std::string predefs =
+      R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%int = OpTypeInt 32 1
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+)";
+
+  const std::string nonEntryFuncs =
+      R"(%13 = OpFunction %void None %12
+%14 = OpLabel
+%15 = OpCopyObject %int %int_0
+OpReturn
+%16 = OpLabel
+%17 = OpCopyObject %int %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%1 = OpFunction %void None %12
+%18 = OpLabel
+OpBranch %19
+%19 = OpLabel
+%20 = OpCopyObject %int %int_2
+%21 = OpFunctionCall %void %13
+%22 = OpCopyObject %int %int_3
+OpLoopMerge %23 %19 None
+OpBranchConditional %true %19 %23
+%23 = OpLabel
+%24 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%1 = OpFunction %void None %12
+%18 = OpLabel
+OpBranch %19
+%19 = OpLabel
+%20 = OpCopyObject %int %int_2
+%25 = OpCopyObject %int %int_0
+OpLoopMerge %23 %19 None
+OpBranch %26
+%27 = OpLabel
+%28 = OpCopyObject %int %int_1
+OpBranch %26
+%26 = OpLabel
+%22 = OpCopyObject %int %int_3
+OpBranchConditional %true %19 %23
+%23 = OpLabel
+%24 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+                                              predefs + nonEntryFuncs + after,
+                                              false, true);
+}
+
+TEST_F(InlineTest, CalleeWithMultiReturnAndPhiRequiresEntryBlockRemapping) {
+  // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/790
+  //
+  // The callee has multiple returns, and so must be wrapped with a single-trip
+  // loop.  That code must remap the callee entry block ID to the introduced
+  // loop body's ID.  Otherwise you can get a dominance error in a cloned OpPhi.
+
+  const std::string predefs =
+      R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+)";
+
+  // This callee has multiple returns, and a Phi in the second block referencing
+  // a value generated in the entry block.
+  const std::string nonEntryFuncs =
+      R"(%12 = OpFunction %void None %9
+%13 = OpLabel
+%14 = OpCopyObject %int %int_0
+OpBranch %15
+%15 = OpLabel
+%16 = OpPhi %int %14 %13
+%17 = OpCopyObject %int %int_1
+OpReturn
+%18 = OpLabel
+%19 = OpCopyObject %int %int_2
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%1 = OpFunction %void None %9
+%20 = OpLabel
+%21 = OpCopyObject %int %int_3
+%22 = OpFunctionCall %void %12
+%23 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%1 = OpFunction %void None %9
+%20 = OpLabel
+%21 = OpCopyObject %int %int_3
+%24 = OpCopyObject %int %int_0
+OpBranch %25
+%25 = OpLabel
+%26 = OpPhi %int %24 %20
+%27 = OpCopyObject %int %int_1
+OpBranch %28
+%29 = OpLabel
+%30 = OpCopyObject %int %int_2
+OpBranch %28
+%28 = OpLabel
+%23 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+                                              predefs + nonEntryFuncs + after,
+                                              false, true);
+}
+
 TEST_F(InlineTest, NonInlinableCalleeWithSingleReturn) {
   // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/2018
   //
@@ -1880,6 +2324,138 @@
       predefs + caller + callee, predefs + caller + callee, false, true);
 }
 
+TEST_F(InlineTest, CalleeWithSingleReturnNeedsSingleTripLoopWrapper) {
+  // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/2018
+  //
+  // The callee has a single return, but needs single-trip loop wrapper
+  // to be inlined because the return is in a selection structure.
+
+  const std::string predefs =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %_GLF_color
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %f_ "f("
+OpName %i "i"
+OpName %_GLF_color "_GLF_color"
+OpDecorate %_GLF_color Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%9 = OpTypeFunction %float
+%float_1 = OpConstant %float 1
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_GLF_color = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%21 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%22 = OpConstantComposite %v4float %float_0 %float_1 %float_0 %float_1
+)";
+
+  const std::string new_predefs =
+      R"(%_ptr_Function_float = OpTypePointer Function %float
+)";
+
+  const std::string main_before =
+      R"(%main = OpFunction %void None %7
+%23 = OpLabel
+%i = OpVariable %_ptr_Function_int Function
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpLoad %int %i
+%29 = OpSLessThan %bool %28 %int_1
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+OpStore %_GLF_color %21
+%31 = OpFunctionCall %float %f_
+OpBranch %26
+%26 = OpLabel
+%32 = OpLoad %int %i
+%33 = OpIAdd %int %32 %int_1
+OpStore %i %33
+OpBranch %24
+%25 = OpLabel
+OpStore %_GLF_color %22
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string main_after =
+      R"(%main = OpFunction %void None %7
+%23 = OpLabel
+%38 = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpLoad %int %i
+%29 = OpSLessThan %bool %28 %int_1
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+OpStore %_GLF_color %21
+OpBranch %39
+%39 = OpLabel
+OpLoopMerge %40 %41 None
+OpBranch %42
+%42 = OpLabel
+OpSelectionMerge %43 None
+OpBranchConditional %true %44 %43
+%44 = OpLabel
+OpStore %38 %float_1
+OpBranch %40
+%43 = OpLabel
+OpStore %38 %float_1
+OpBranch %40
+%41 = OpLabel
+OpBranchConditional %false %39 %40
+%40 = OpLabel
+%31 = OpLoad %float %38
+OpBranch %26
+%26 = OpLabel
+%32 = OpLoad %int %i
+%33 = OpIAdd %int %32 %int_1
+OpStore %i %33
+OpBranch %24
+%25 = OpLabel
+OpStore %_GLF_color %22
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string callee =
+      R"(%f_ = OpFunction %float None %9
+%34 = OpLabel
+OpSelectionMerge %35 None
+OpBranchConditional %true %36 %35
+%36 = OpLabel
+OpReturnValue %float_1
+%35 = OpLabel
+OpReturnValue %float_1
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(
+      predefs + main_before + callee,
+      predefs + new_predefs + main_after + callee, false, true);
+}
+
 TEST_F(InlineTest, Decorated1) {
   // Same test as Simple with the difference
   // that OpFAdd in the outlined function is
@@ -1950,7 +2526,7 @@
 )";
 
   const std::string after =
-      R"(OpDecorate %38 RelaxedPrecision
+      R"(OpDecorate %37 RelaxedPrecision
 %void = OpTypeVoid
 %11 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -1972,12 +2548,12 @@
 %param = OpVariable %_ptr_Function_v4float Function
 %23 = OpLoad %v4float %BaseColor
 OpStore %param %23
-%34 = OpAccessChain %_ptr_Function_float %param %uint_0
-%35 = OpLoad %float %34
-%36 = OpAccessChain %_ptr_Function_float %param %uint_1
-%37 = OpLoad %float %36
-%38 = OpFAdd %float %35 %37
-OpStore %32 %38
+%33 = OpAccessChain %_ptr_Function_float %param %uint_0
+%34 = OpLoad %float %33
+%35 = OpAccessChain %_ptr_Function_float %param %uint_1
+%36 = OpLoad %float %35
+%37 = OpFAdd %float %34 %36
+OpStore %32 %37
 %24 = OpLoad %float %32
 %25 = OpCompositeConstruct %v4float %24 %24 %24 %24
 OpStore %color %25
@@ -2096,12 +2672,12 @@
 %param = OpVariable %_ptr_Function_v4float Function
 %22 = OpLoad %v4float %BaseColor
 OpStore %param %22
-%34 = OpAccessChain %_ptr_Function_float %param %uint_0
-%35 = OpLoad %float %34
-%36 = OpAccessChain %_ptr_Function_float %param %uint_1
-%37 = OpLoad %float %36
-%38 = OpFAdd %float %35 %37
-OpStore %32 %38
+%33 = OpAccessChain %_ptr_Function_float %param %uint_0
+%34 = OpLoad %float %33
+%35 = OpAccessChain %_ptr_Function_float %param %uint_1
+%36 = OpLoad %float %35
+%37 = OpFAdd %float %34 %36
+OpStore %32 %37
 %23 = OpLoad %float %32
 %24 = OpCompositeConstruct %v4float %23 %23 %23 %23
 OpStore %color %24
@@ -2441,7 +3017,7 @@
 %main = OpFunction %void None %3
 %5 = OpLabel
 OpKill
-%18 = OpLabel
+%17 = OpLabel
 OpReturn
 OpFunctionEnd
 %kill_ = OpFunction %void None %3
@@ -2454,560 +3030,6 @@
   SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true);
 }
 
-TEST_F(InlineTest, EarlyReturnFunctionInlined) {
-  // #version 140
-  //
-  // in vec4 BaseColor;
-  //
-  // float foo(vec4 bar)
-  // {
-  //     if (bar.x < 0.0)
-  //         return 0.0;
-  //     return bar.x;
-  // }
-  //
-  // void main()
-  // {
-  //     vec4 color = vec4(foo(BaseColor));
-  //     gl_FragColor = color;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-OpName %main "main"
-OpName %foo_vf4_ "foo(vf4;"
-OpName %bar "bar"
-OpName %color "color"
-OpName %BaseColor "BaseColor"
-OpName %param "param"
-OpName %gl_FragColor "gl_FragColor"
-%void = OpTypeVoid
-%10 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v4float = OpTypeVector %float 4
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%14 = OpTypeFunction %float %_ptr_Function_v4float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%_ptr_Function_float = OpTypePointer Function %float
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%_ptr_Input_v4float = OpTypePointer Input %v4float
-%BaseColor = OpVariable %_ptr_Input_v4float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%gl_FragColor = OpVariable %_ptr_Output_v4float Output
-)";
-
-  const std::string foo =
-      R"(%foo_vf4_ = OpFunction %float None %14
-%bar = OpFunctionParameter %_ptr_Function_v4float
-%27 = OpLabel
-%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%29 = OpLoad %float %28
-%30 = OpFOrdLessThan %bool %29 %float_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %32 %31
-%32 = OpLabel
-OpReturnValue %float_0
-%31 = OpLabel
-%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%34 = OpLoad %float %33
-OpReturnValue %34
-OpFunctionEnd
-)";
-
-  const std::string fooMergeReturn =
-      R"(%foo_vf4_ = OpFunction %float None %14
-%bar = OpFunctionParameter %_ptr_Function_v4float
-%27 = OpLabel
-%41 = OpVariable %_ptr_Function_bool Function %false
-%36 = OpVariable %_ptr_Function_float Function
-OpSelectionMerge %35 None
-OpSwitch %uint_0 %38
-%38 = OpLabel
-%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%29 = OpLoad %float %28
-%30 = OpFOrdLessThan %bool %29 %float_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %32 %31
-%32 = OpLabel
-OpStore %41 %true
-OpStore %36 %float_0
-OpBranch %35
-%31 = OpLabel
-%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%34 = OpLoad %float %33
-OpStore %41 %true
-OpStore %36 %34
-OpBranch %35
-%35 = OpLabel
-%37 = OpLoad %float %36
-OpReturnValue %37
-OpFunctionEnd
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %10
-%22 = OpLabel
-%color = OpVariable %_ptr_Function_v4float Function
-%param = OpVariable %_ptr_Function_v4float Function
-%23 = OpLoad %v4float %BaseColor
-OpStore %param %23
-%24 = OpFunctionCall %float %foo_vf4_ %param
-%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
-OpStore %color %25
-%26 = OpLoad %v4float %color
-OpStore %gl_FragColor %26
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%false = OpConstantFalse %bool
-%_ptr_Function_bool = OpTypePointer Function %bool
-%true = OpConstantTrue %bool
-%main = OpFunction %void None %10
-%22 = OpLabel
-%43 = OpVariable %_ptr_Function_bool Function %false
-%44 = OpVariable %_ptr_Function_float Function
-%45 = OpVariable %_ptr_Function_float Function
-%color = OpVariable %_ptr_Function_v4float Function
-%param = OpVariable %_ptr_Function_v4float Function
-%23 = OpLoad %v4float %BaseColor
-OpStore %param %23
-OpStore %43 %false
-OpSelectionMerge %55 None
-OpSwitch %uint_0 %47
-%47 = OpLabel
-%48 = OpAccessChain %_ptr_Function_float %param %uint_0
-%49 = OpLoad %float %48
-%50 = OpFOrdLessThan %bool %49 %float_0
-OpSelectionMerge %52 None
-OpBranchConditional %50 %51 %52
-%51 = OpLabel
-OpStore %43 %true
-OpStore %44 %float_0
-OpBranch %55
-%52 = OpLabel
-%53 = OpAccessChain %_ptr_Function_float %param %uint_0
-%54 = OpLoad %float %53
-OpStore %43 %true
-OpStore %44 %54
-OpBranch %55
-%55 = OpLabel
-%56 = OpLoad %float %44
-OpStore %45 %56
-%24 = OpLoad %float %45
-%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
-OpStore %color %25
-%26 = OpLoad %v4float %color
-OpStore %gl_FragColor %26
-OpReturn
-OpFunctionEnd
-)";
-
-  // The early return case must be handled by merge-return first.
-  AddPass<MergeReturnPass>();
-  AddPass<InlineExhaustivePass>();
-  RunAndCheck(predefs + before + foo, predefs + after + fooMergeReturn);
-}
-
-TEST_F(InlineTest, EarlyReturnNotAppearingLastInFunctionInlined) {
-  // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/755
-  //
-  // Original example is derived from:
-  //
-  // #version 450
-  //
-  // float foo() {
-  //     if (true) {
-  //     }
-  // }
-  //
-  // void main() { foo(); }
-  //
-  // But the order of basic blocks in foo is changed so that the return
-  // block is listed second-last.  There is only one return in the callee
-  // but it does not appear last.
-
-  const std::string predefs =
-      R"(OpCapability Shader
-OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %main "main"
-OpSource GLSL 450
-OpName %main "main"
-OpName %foo_ "foo("
-%void = OpTypeVoid
-%4 = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-)";
-
-  const std::string foo =
-      R"(%foo_ = OpFunction %void None %4
-%7 = OpLabel
-OpSelectionMerge %8 None
-OpBranchConditional %true %9 %8
-%8 = OpLabel
-OpReturn
-%9 = OpLabel
-OpBranch %8
-OpFunctionEnd
-)";
-
-  const std::string fooMergeReturn =
-      R"(%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%false = OpConstantFalse %bool
-%_ptr_Function_bool = OpTypePointer Function %bool
-%foo_ = OpFunction %void None %4
-%7 = OpLabel
-%18 = OpVariable %_ptr_Function_bool Function %false
-OpSelectionMerge %12 None
-OpSwitch %uint_0 %13
-%13 = OpLabel
-OpSelectionMerge %8 None
-OpBranchConditional %true %9 %8
-%8 = OpLabel
-OpStore %18 %true
-OpBranch %12
-%9 = OpLabel
-OpBranch %8
-%12 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %4
-%10 = OpLabel
-%11 = OpFunctionCall %void %foo_
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %4
-%10 = OpLabel
-%19 = OpVariable %_ptr_Function_bool Function %false
-OpStore %19 %false
-OpSelectionMerge %24 None
-OpSwitch %uint_0 %21
-%21 = OpLabel
-OpSelectionMerge %22 None
-OpBranchConditional %true %23 %22
-%22 = OpLabel
-OpStore %19 %true
-OpBranch %24
-%23 = OpLabel
-OpBranch %22
-%24 = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  // The early return case must be handled by merge-return first.
-  AddPass<MergeReturnPass>();
-  AddPass<InlineExhaustivePass>();
-  RunAndCheck(predefs + foo + before, predefs + fooMergeReturn + after);
-}
-
-TEST_F(InlineTest, CalleeWithSingleReturnNeedsSingleTripLoopWrapper) {
-  // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/2018
-  //
-  // The callee has a single return, but needs single-trip loop wrapper
-  // to be inlined because the return is in a selection structure.
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main" %_GLF_color
-OpExecutionMode %main OriginUpperLeft
-OpSource ESSL 310
-OpName %main "main"
-OpName %f_ "f("
-OpName %i "i"
-OpName %_GLF_color "_GLF_color"
-OpDecorate %_GLF_color Location 0
-%void = OpTypeVoid
-%7 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%9 = OpTypeFunction %float
-%float_1 = OpConstant %float 1
-%bool = OpTypeBool
-%false = OpConstantFalse %bool
-%true = OpConstantTrue %bool
-%int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
-%int_0 = OpConstant %int 0
-%int_1 = OpConstant %int 1
-%v4float = OpTypeVector %float 4
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_GLF_color = OpVariable %_ptr_Output_v4float Output
-%float_0 = OpConstant %float 0
-%21 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
-%22 = OpConstantComposite %v4float %float_0 %float_1 %float_0 %float_1
-)";
-
-  const std::string new_predefs =
-      R"(%_ptr_Function_float = OpTypePointer Function %float
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%_ptr_Function_bool = OpTypePointer Function %bool
-)";
-
-  const std::string main_before =
-      R"(%main = OpFunction %void None %7
-%23 = OpLabel
-%i = OpVariable %_ptr_Function_int Function
-OpStore %i %int_0
-OpBranch %24
-%24 = OpLabel
-OpLoopMerge %25 %26 None
-OpBranch %27
-%27 = OpLabel
-%28 = OpLoad %int %i
-%29 = OpSLessThan %bool %28 %int_1
-OpBranchConditional %29 %30 %25
-%30 = OpLabel
-OpStore %_GLF_color %21
-%31 = OpFunctionCall %float %f_
-OpBranch %26
-%26 = OpLabel
-%32 = OpLoad %int %i
-%33 = OpIAdd %int %32 %int_1
-OpStore %i %33
-OpBranch %24
-%25 = OpLabel
-OpStore %_GLF_color %22
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string main_after =
-      R"(%main = OpFunction %void None %7
-%23 = OpLabel
-%46 = OpVariable %_ptr_Function_bool Function %false
-%47 = OpVariable %_ptr_Function_float Function
-%48 = OpVariable %_ptr_Function_float Function
-%i = OpVariable %_ptr_Function_int Function
-OpStore %i %int_0
-OpBranch %24
-%24 = OpLabel
-OpLoopMerge %25 %26 None
-OpBranch %27
-%27 = OpLabel
-%28 = OpLoad %int %i
-%29 = OpSLessThan %bool %28 %int_1
-OpBranchConditional %29 %30 %25
-%30 = OpLabel
-OpStore %_GLF_color %21
-OpStore %46 %false
-OpSelectionMerge %53 None
-OpSwitch %uint_0 %50
-%50 = OpLabel
-OpSelectionMerge %52 None
-OpBranchConditional %true %51 %52
-%51 = OpLabel
-OpStore %46 %true
-OpStore %47 %float_1
-OpBranch %53
-%52 = OpLabel
-OpStore %46 %true
-OpStore %47 %float_1
-OpBranch %53
-%53 = OpLabel
-%54 = OpLoad %float %47
-OpStore %48 %54
-%31 = OpLoad %float %48
-OpBranch %26
-%26 = OpLabel
-%32 = OpLoad %int %i
-%33 = OpIAdd %int %32 %int_1
-OpStore %i %33
-OpBranch %24
-%25 = OpLabel
-OpStore %_GLF_color %22
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string callee =
-      R"(%f_ = OpFunction %float None %9
-%34 = OpLabel
-OpSelectionMerge %35 None
-OpBranchConditional %true %36 %35
-%36 = OpLabel
-OpReturnValue %float_1
-%35 = OpLabel
-OpReturnValue %float_1
-OpFunctionEnd
-)";
-
-  const std::string calleeMergeReturn =
-      R"(%f_ = OpFunction %float None %9
-%34 = OpLabel
-%45 = OpVariable %_ptr_Function_bool Function %false
-%39 = OpVariable %_ptr_Function_float Function
-OpSelectionMerge %37 None
-OpSwitch %uint_0 %41
-%41 = OpLabel
-OpSelectionMerge %35 None
-OpBranchConditional %true %36 %35
-%36 = OpLabel
-OpStore %45 %true
-OpStore %39 %float_1
-OpBranch %37
-%35 = OpLabel
-OpStore %45 %true
-OpStore %39 %float_1
-OpBranch %37
-%37 = OpLabel
-%40 = OpLoad %float %39
-OpReturnValue %40
-OpFunctionEnd
-)";
-
-  // The early return case must be handled by merge-return first.
-  AddPass<MergeReturnPass>();
-  AddPass<InlineExhaustivePass>();
-  RunAndCheck(predefs + main_before + callee,
-              predefs + new_predefs + main_after + calleeMergeReturn);
-}
-
-TEST_F(InlineTest, ForwardReferencesInPhiInlined) {
-  // The basic structure of the test case is like this:
-  //
-  // int foo() {
-  //   int result = 1;
-  //   if (true) {
-  //      result = 1;
-  //   }
-  //   return result;
-  // }
-  //
-  // void main() {
-  //  int x = foo();
-  // }
-  //
-  // but with modifications: Using Phi instead of load/store, and the
-  // return block in foo appears before the "then" block.
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Vertex %main "main"
-OpSource GLSL 450
-OpName %main "main"
-OpName %foo_ "foo("
-OpName %x "x"
-%void = OpTypeVoid
-%6 = OpTypeFunction %void
-%int = OpTypeInt 32 1
-%8 = OpTypeFunction %int
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%int_0 = OpConstant %int 0
-%_ptr_Function_int = OpTypePointer Function %int
-)";
-
-  const std::string callee =
-      R"(%foo_ = OpFunction %int None %8
-%13 = OpLabel
-%14 = OpCopyObject %int %int_0
-OpSelectionMerge %15 None
-OpBranchConditional %true %16 %15
-%15 = OpLabel
-%17 = OpPhi %int %14 %13 %18 %16
-OpReturnValue %17
-%16 = OpLabel
-%18 = OpCopyObject %int %int_0
-OpBranch %15
-OpFunctionEnd
-)";
-
-  const std::string calleeMergeReturn =
-      R"(%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%false = OpConstantFalse %bool
-%_ptr_Function_bool = OpTypePointer Function %bool
-%foo_ = OpFunction %int None %8
-%13 = OpLabel
-%29 = OpVariable %_ptr_Function_bool Function %false
-%22 = OpVariable %_ptr_Function_int Function
-OpSelectionMerge %21 None
-OpSwitch %uint_0 %24
-%24 = OpLabel
-%14 = OpCopyObject %int %int_0
-OpSelectionMerge %15 None
-OpBranchConditional %true %16 %15
-%15 = OpLabel
-%17 = OpPhi %int %14 %24 %18 %16
-OpStore %29 %true
-OpStore %22 %17
-OpBranch %21
-%16 = OpLabel
-%18 = OpCopyObject %int %int_0
-OpBranch %15
-%21 = OpLabel
-%23 = OpLoad %int %22
-OpReturnValue %23
-OpFunctionEnd
-)";
-
-  const std::string before =
-      R"(%main = OpFunction %void None %6
-%19 = OpLabel
-%x = OpVariable %_ptr_Function_int Function
-%20 = OpFunctionCall %int %foo_
-OpStore %x %20
-OpReturn
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%main = OpFunction %void None %6
-%19 = OpLabel
-%30 = OpVariable %_ptr_Function_bool Function %false
-%31 = OpVariable %_ptr_Function_int Function
-%32 = OpVariable %_ptr_Function_int Function
-%x = OpVariable %_ptr_Function_int Function
-OpStore %30 %false
-OpSelectionMerge %40 None
-OpSwitch %uint_0 %34
-%34 = OpLabel
-%35 = OpCopyObject %int %int_0
-OpSelectionMerge %36 None
-OpBranchConditional %true %38 %36
-%36 = OpLabel
-%37 = OpPhi %int %35 %34 %39 %38
-OpStore %30 %true
-OpStore %31 %37
-OpBranch %40
-%38 = OpLabel
-%39 = OpCopyObject %int %int_0
-OpBranch %36
-%40 = OpLabel
-%41 = OpLoad %int %31
-OpStore %32 %41
-%20 = OpLoad %int %32
-OpStore %x %20
-OpReturn
-OpFunctionEnd
-)";
-
-  AddPass<MergeReturnPass>();
-  AddPass<InlineExhaustivePass>();
-  RunAndCheck(predefs + callee + before, predefs + calleeMergeReturn + after);
-}
-
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Empty modules