Rewriting dead branch elimination.
authorAlan Baker <alanbaker@google.com>
Thu, 4 Jan 2018 22:04:03 +0000 (17:04 -0500)
committerDavid Neto <dneto@google.com>
Tue, 9 Jan 2018 17:21:39 +0000 (12:21 -0500)
Pass now paints live blocks and fixes constant branches and switches as
it goes. No longer requires structured control flow. It also removes
unreachable blocks as a side effect. It fixes the IR (phis) before doing
any code removal (other than terminator changes).

Added several unit tests for updated/new functionality.

Does not remove dead edge from a phi node:
* Checks that incoming edges are live in order to retain them
* Added BasicBlock::IsSuccessor
* added test

Fixing phi updates in the presence of extra backedge blocks

* Added tests to catch bug

Reworked how phis are updated

* Instead of creating a new Phi and RAUW'ing the old phi with it, I now
replace the phi operands, but maintain the def/use manager correctly.

For unreachable merge:

* When considering unreachable continue blocks the code now properly
checks whether the incoming edge will continue to be live.

Major refactoring for review

* Broke into 4 major functions
 * marking live blocks
 * marking structured targets
 * fixing phis
 * deleting blocks

include/spirv-tools/optimizer.hpp
source/opt/basic_block.cpp
source/opt/basic_block.h
source/opt/dead_branch_elim_pass.cpp
source/opt/dead_branch_elim_pass.h
source/opt/mem_pass.cpp
source/opt/mem_pass.h
test/opt/dead_branch_elim_test.cpp

index b909648..c6f74c5 100644 (file)
@@ -295,9 +295,8 @@ Optimizer::PassToken CreateLocalSingleBlockLoadStoreElimPass();
 // For all phi functions in merge block, replace all uses with the id
 // corresponding to the living predecessor.
 //
-// This pass only works on shaders (guaranteed to have structured control
-// flow). Note that some such branches and blocks may be left to avoid
-// creating invalid control flow. Improving this is left to future work.
+// Note that some branches and blocks may be left to avoid creating invalid
+// control flow. Improving this is left to future work.
 //
 // This pass is most effective when preceeded by passes which eliminate
 // local loads and stores, effectively propagating constant values where
index e1d7d7c..77ee133 100644 (file)
@@ -87,7 +87,7 @@ Instruction* BasicBlock::GetLoopMergeInst() {
 }
 
 void BasicBlock::ForEachSuccessorLabel(
-    const std::function<void(const uint32_t)>& f) {
+    const std::function<void(const uint32_t)>& f) const {
   const auto br = &insts_.back();
   switch (br->opcode()) {
     case SpvOpBranch: {
@@ -106,6 +106,15 @@ void BasicBlock::ForEachSuccessorLabel(
   }
 }
 
+bool BasicBlock::IsSuccessor(const ir::BasicBlock* block) const {
+  uint32_t succId = block->id();
+  bool isSuccessor = false;
+  ForEachSuccessorLabel([&isSuccessor, succId](const uint32_t label) {
+    if (label == succId) isSuccessor = true;
+  });
+  return isSuccessor;
+}
+
 void BasicBlock::ForMergeAndContinueLabel(
     const std::function<void(const uint32_t)>& f) {
   auto ii = insts_.end();
index 6b87a98..cfff902 100644 (file)
@@ -116,7 +116,11 @@ class BasicBlock {
                              bool run_on_debug_line_insts = false);
 
   // Runs the given function |f| on each label id of each successor block
-  void ForEachSuccessorLabel(const std::function<void(const uint32_t)>& f);
+  void ForEachSuccessorLabel(
+      const std::function<void(const uint32_t)>& f) const;
+
+  // Returns true if |block| is a direct successor of |this|.
+  bool IsSuccessor(const ir::BasicBlock* block) const;
 
   // Runs the given function |f| on the merge and continue label, if any
   void ForMergeAndContinueLabel(const std::function<void(const uint32_t)>& f);
index a8c3068..0ac61cc 100644 (file)
@@ -1,6 +1,7 @@
 // Copyright (c) 2017 The Khronos Group Inc.
 // Copyright (c) 2017 Valve Corporation
 // Copyright (c) 2017 LunarG Inc.
+// Copyright (c) 2018 Google Inc.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
 #include "cfa.h"
 #include "ir_context.h"
 #include "iterator.h"
+#include "make_unique.h"
 
 namespace spvtools {
 namespace opt {
 
 namespace {
 
-const uint32_t kBranchTargetLabIdInIdx = 0;
 const uint32_t kBranchCondTrueLabIdInIdx = 1;
 const uint32_t kBranchCondFalseLabIdInIdx = 2;
-const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
 
 }  // anonymous namespace
 
@@ -73,6 +73,7 @@ bool DeadBranchElimPass::GetConstInteger(uint32_t selId, uint32_t* selVal) {
 }
 
 void DeadBranchElimPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) {
+  assert(get_def_use_mgr()->GetDef(labelId) != nullptr);
   std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
       context(), SpvOpBranch, 0, 0,
       {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}}));
@@ -80,253 +81,259 @@ void DeadBranchElimPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) {
   bp->AddInstruction(std::move(newBranch));
 }
 
-void DeadBranchElimPass::AddSelectionMerge(uint32_t labelId,
-                                           ir::BasicBlock* bp) {
-  std::unique_ptr<ir::Instruction> newMerge(new ir::Instruction(
-      context(), SpvOpSelectionMerge, 0, 0,
-      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}},
-       {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {0}}}));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*newMerge);
-  bp->AddInstruction(std::move(newMerge));
+ir::BasicBlock* DeadBranchElimPass::GetParentBlock(uint32_t id) {
+  return context()->get_instr_block(get_def_use_mgr()->GetDef(id));
 }
 
-void DeadBranchElimPass::AddBranchConditional(uint32_t condId,
-                                              uint32_t trueLabId,
-                                              uint32_t falseLabId,
-                                              ir::BasicBlock* bp) {
-  std::unique_ptr<ir::Instruction> newBranchCond(new ir::Instruction(
-      context(), SpvOpBranchConditional, 0, 0,
-      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {condId}},
-       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {trueLabId}},
-       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {falseLabId}}}));
-  get_def_use_mgr()->AnalyzeInstDefUse(&*newBranchCond);
-  bp->AddInstruction(std::move(newBranchCond));
-}
+bool DeadBranchElimPass::MarkLiveBlocks(
+    ir::Function* func, std::unordered_set<ir::BasicBlock*>* live_blocks) {
+  std::unordered_set<ir::BasicBlock*> continues;
+  std::vector<ir::BasicBlock*> stack;
+  stack.push_back(&*func->begin());
+  bool modified = false;
+  while (!stack.empty()) {
+    ir::BasicBlock* block = stack.back();
+    stack.pop_back();
 
-bool DeadBranchElimPass::GetSelectionBranch(ir::BasicBlock* bp,
-                                            ir::Instruction** branchInst,
-                                            ir::Instruction** mergeInst,
-                                            uint32_t* condId) {
-  auto ii = bp->end();
-  --ii;
-  *branchInst = &*ii;
-  if (ii == bp->begin()) return false;
-  --ii;
-  *mergeInst = &*ii;
-  if ((*mergeInst)->opcode() != SpvOpSelectionMerge) return false;
-  // SPIR-V says the terminator for an OpSelectionMerge must be
-  // either a conditional branch or a switch.
-  assert((*branchInst)->opcode() == SpvOpBranchConditional ||
-         (*branchInst)->opcode() == SpvOpSwitch);
-  // Both BranchConidtional and Switch have their conditional value at 0.
-  *condId = (*branchInst)->GetSingleWordInOperand(0);
-  return true;
-}
+    // Live blocks doubles as visited set.
+    if (!live_blocks->insert(block).second) continue;
 
-bool DeadBranchElimPass::HasNonPhiNonBackedgeRef(uint32_t labelId) {
-  bool nonPhiNonBackedgeRef = false;
-  get_def_use_mgr()->ForEachUser(
-      labelId, [this, &nonPhiNonBackedgeRef](ir::Instruction* user) {
-        if (user->opcode() != SpvOpPhi &&
-            backedges_.find(user) == backedges_.end()) {
-          nonPhiNonBackedgeRef = true;
-        }
+    uint32_t cont_id = block->ContinueBlockIdIfAny();
+    if (cont_id != 0) continues.insert(GetParentBlock(cont_id));
+
+    ir::Instruction* terminator = block->terminator();
+    uint32_t live_lab_id = 0;
+    // Check if the terminator has a single valid successor.
+    if (terminator->opcode() == SpvOpBranchConditional) {
+      bool condVal;
+      if (GetConstCondition(terminator->GetSingleWordInOperand(0u), &condVal)) {
+        live_lab_id = terminator->GetSingleWordInOperand(
+            condVal ? kBranchCondTrueLabIdInIdx : kBranchCondFalseLabIdInIdx);
+      }
+    } else if (terminator->opcode() == SpvOpSwitch) {
+      uint32_t sel_val;
+      if (GetConstInteger(terminator->GetSingleWordInOperand(0u), &sel_val)) {
+        // Search switch operands for selector value, set live_lab_id to
+        // corresponding label, use default if not found.
+        uint32_t icnt = 0;
+        uint32_t case_val;
+        terminator->ForEachInOperand(
+            [&icnt, &case_val, &sel_val, &live_lab_id](const uint32_t* idp) {
+              if (icnt == 1) {
+                // Start with default label.
+                live_lab_id = *idp;
+              } else if (icnt > 1) {
+                if (icnt % 2 == 0) {
+                  case_val = *idp;
+                } else {
+                  if (case_val == sel_val) live_lab_id = *idp;
+                }
+              }
+              ++icnt;
+            });
+      }
+    }
+
+    // Don't simplify branches of continue blocks. A path from the continue to
+    // the header is required.
+    // TODO(alan-baker): They can be simplified iff there remains a path to the
+    // backedge. Structured control flow should guarantee one path hits the
+    // backedge, but I've removed the requirement for structured control flow
+    // from this pass.
+    bool simplify = live_lab_id != 0 && !continues.count(block);
+
+    if (simplify) {
+      modified = true;
+      // Replace with unconditional branch.
+      // Remove the merge instruction if it is a selection merge.
+      AddBranch(live_lab_id, block);
+      context()->KillInst(terminator);
+      ir::Instruction* mergeInst = block->GetMergeInst();
+      if (mergeInst->opcode() == SpvOpSelectionMerge) {
+        context()->KillInst(mergeInst);
+      }
+      stack.push_back(GetParentBlock(live_lab_id));
+    } else {
+      // All successors are live.
+      block->ForEachSuccessorLabel([&stack, this](const uint32_t label) {
+        stack.push_back(GetParentBlock(label));
       });
-  return nonPhiNonBackedgeRef;
+    }
+  }
+
+  return modified;
 }
 
-void DeadBranchElimPass::ComputeBackEdges(
-    std::list<ir::BasicBlock*>& structuredOrder) {
-  backedges_.clear();
-  std::unordered_set<uint32_t> visited;
-  // In structured order, edges to visited blocks are back edges
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
-    visited.insert((*bi)->id());
-    auto ii = (*bi)->end();
-    --ii;
-    switch (ii->opcode()) {
-      case SpvOpBranch: {
-        const uint32_t labId =
-            ii->GetSingleWordInOperand(kBranchTargetLabIdInIdx);
-        if (visited.find(labId) != visited.end()) backedges_.insert(&*ii);
-      } break;
-      case SpvOpBranchConditional: {
-        const uint32_t tLabId =
-            ii->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx);
-        if (visited.find(tLabId) != visited.end()) {
-          backedges_.insert(&*ii);
-          break;
+void DeadBranchElimPass::MarkUnreachableStructuredTargets(
+    const std::unordered_set<ir::BasicBlock*>& live_blocks,
+    std::unordered_set<ir::BasicBlock*>* unreachable_merges,
+    std::unordered_map<ir::BasicBlock*, ir::BasicBlock*>*
+        unreachable_continues) {
+  for (auto block : live_blocks) {
+    if (auto merge_id = block->MergeBlockIdIfAny()) {
+      ir::BasicBlock* merge_block = GetParentBlock(merge_id);
+      if (!live_blocks.count(merge_block)) {
+        unreachable_merges->insert(merge_block);
+      }
+      if (auto cont_id = block->ContinueBlockIdIfAny()) {
+        ir::BasicBlock* cont_block = GetParentBlock(cont_id);
+        if (!live_blocks.count(cont_block)) {
+          (*unreachable_continues)[cont_block] = block;
         }
-        const uint32_t fLabId =
-            ii->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
-        if (visited.find(fLabId) != visited.end()) backedges_.insert(&*ii);
-      } break;
-      default:
-        break;
+      }
     }
   }
 }
 
-bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) {
-  // Traverse blocks in structured order
-  std::list<ir::BasicBlock*> structuredOrder;
-  cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder);
-  ComputeBackEdges(structuredOrder);
-  std::unordered_set<ir::BasicBlock*> elimBlocks;
+bool DeadBranchElimPass::FixPhiNodesInLiveBlocks(
+    ir::Function* func, const std::unordered_set<ir::BasicBlock*>& live_blocks,
+    const std::unordered_map<ir::BasicBlock*, ir::BasicBlock*>&
+        unreachable_continues) {
   bool modified = false;
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
-    // Skip blocks that are already in the elimination set
-    if (elimBlocks.find(*bi) != elimBlocks.end()) continue;
-    // Skip blocks that don't have conditional branch preceded
-    // by OpSelectionMerge
-    ir::Instruction* br;
-    ir::Instruction* mergeInst;
-    uint32_t condId;
-    if (!GetSelectionBranch(*bi, &br, &mergeInst, &condId)) continue;
-
-    // If constant condition/selector, replace conditional branch/switch
-    // with unconditional branch and delete merge
-    uint32_t liveLabId;
-    if (br->opcode() == SpvOpBranchConditional) {
-      bool condVal;
-      if (!GetConstCondition(condId, &condVal)) continue;
-      liveLabId = (condVal == true)
-                      ? br->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx)
-                      : br->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx);
-    } else {
-      assert(br->opcode() == SpvOpSwitch);
-      // Search switch operands for selector value, set liveLabId to
-      // corresponding label, use default if not found
-      uint32_t selVal;
-      if (!GetConstInteger(condId, &selVal)) continue;
-      uint32_t icnt = 0;
-      uint32_t caseVal;
-      br->ForEachInOperand(
-          [&icnt, &caseVal, &selVal, &liveLabId](const uint32_t* idp) {
-            if (icnt == 1) {
-              // Start with default label
-              liveLabId = *idp;
-            } else if (icnt > 1) {
-              if (icnt % 2 == 0) {
-                caseVal = *idp;
-              } else {
-                if (caseVal == selVal) liveLabId = *idp;
-              }
-            }
-            ++icnt;
-          });
-    }
+  for (auto& block : *func) {
+    if (live_blocks.count(&block)) {
+      for (auto iter = block.begin(); iter != block.end();) {
+        if (iter->opcode() != SpvOpPhi) {
+          break;
+        }
 
-    const uint32_t mergeLabId =
-        mergeInst->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
-    AddBranch(liveLabId, *bi);
-    backedges_.erase(br);
-    context()->KillInst(br);
-    context()->KillInst(mergeInst);
-
-    modified = true;
-
-    // Iterate to merge block adding dead blocks to elimination set
-    auto dbi = bi;
-    ++dbi;
-    uint32_t dLabId = (*dbi)->id();
-    while (dLabId != mergeLabId) {
-      if (!HasNonPhiNonBackedgeRef(dLabId)) {
-        // Kill use/def for all instructions and mark block for elimination
-        backedges_.erase((*dbi)->terminator());
-        KillAllInsts(*dbi);
-        elimBlocks.insert(*dbi);
-      }
-      ++dbi;
-      dLabId = (*dbi)->id();
-    }
+        bool changed = false;
+        bool backedge_added = false;
+        ir::Instruction* inst = &*iter;
+        std::vector<ir::Operand> operands;
+        // Build a complete set of operands (not just input operands). Start
+        // with type and result id operands.
+        operands.push_back(inst->GetOperand(0u));
+        operands.push_back(inst->GetOperand(1u));
+        // Iterate through the incoming labels and determine which to keep
+        // and/or modify.
+        for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) {
+          ir::BasicBlock* inc = GetParentBlock(inst->GetSingleWordInOperand(i));
+          auto cont_iter = unreachable_continues.find(inc);
+          if (cont_iter != unreachable_continues.end() &&
+              cont_iter->second == &block) {
+            // Replace incoming value with undef if this phi exists in the loop
+            // header. Otherwise, this edge is not live since the unreachable
+            // continue block will be replaced with an unconditional branch to
+            // the header only.
+            operands.emplace_back(
+                SPV_OPERAND_TYPE_ID,
+                std::initializer_list<uint32_t>{Type2Undef(inst->type_id())});
+            operands.push_back(inst->GetInOperand(i));
+            changed = true;
+            backedge_added = true;
+          } else if (live_blocks.count(inc) && inc->IsSuccessor(&block)) {
+            // Keep live incoming edge.
+            operands.push_back(inst->GetInOperand(i - 1));
+            operands.push_back(inst->GetInOperand(i));
+          } else {
+            // Remove incoming edge.
+            changed = true;
+          }
+        }
 
-    // If merge block is unreachable, continue eliminating blocks until
-    // a live block or last block is reached.
-    while (!HasNonPhiNonBackedgeRef(dLabId)) {
-      backedges_.erase((*dbi)->terminator());
-      KillAllInsts(*dbi);
-      elimBlocks.insert(*dbi);
-      ++dbi;
-      if (dbi == structuredOrder.end()) break;
-      dLabId = (*dbi)->id();
-    }
+        if (changed) {
+          uint32_t continue_id = block.ContinueBlockIdIfAny();
+          if (!backedge_added && continue_id != 0 &&
+              unreachable_continues.count(GetParentBlock(continue_id))) {
+            // Changed the backedge to branch from the continue block instead
+            // of a successor of the continue block. Add an entry to the phi to
+            // provide an undef for the continue block. Since the successor of
+            // the continue must also be unreachable (dominated by the continue
+            // block), any entry for the original backedge has been removed
+            // from the phi operands.
+            operands.emplace_back(
+                SPV_OPERAND_TYPE_ID,
+                std::initializer_list<uint32_t>{Type2Undef(inst->type_id())});
+            operands.emplace_back(SPV_OPERAND_TYPE_ID,
+                                  std::initializer_list<uint32_t>{continue_id});
+          }
 
-    // If last block reached, look for next dead branch
-    if (dbi == structuredOrder.end()) continue;
-
-    // Create set of dead predecessors in preparation for phi update.
-    // Add the header block if the live branch is not the merge block.
-    std::unordered_set<ir::BasicBlock*> deadPreds(elimBlocks);
-    if (liveLabId != dLabId) deadPreds.insert(*bi);
-
-    // Update phi instructions in terminating block.
-    ir::Instruction* inst = &*(*dbi)->begin();
-    while (inst) {
-      // Skip NoOps, break at end of phis
-      SpvOp op = inst->opcode();
-      if (op == SpvOpNop) {
-        inst = inst->NextNode();
-        continue;
-      }
-      if (op != SpvOpPhi) break;
-      // Count phi's live predecessors with lcnt and remember last one
-      // with lidx.
-      uint32_t lcnt = 0;
-      uint32_t lidx = 0;
-      uint32_t icnt = 0;
-      inst->ForEachInId([&deadPreds, &icnt, &lcnt, &lidx, this](uint32_t* idp) {
-        if (icnt % 2 == 1) {
-          if (deadPreds.find(cfg()->block(*idp)) == deadPreds.end()) {
-            ++lcnt;
-            lidx = icnt - 1;
+          // Either replace the phi with a single value or rebuild the phi out
+          // of |operands|.
+          //
+          // We always have type and result id operands. So this phi has a
+          // single source if there are two more operands beyond those.
+          if (operands.size() == 4) {
+            // First input data operands is at index 2.
+            uint32_t replId = operands[2u].words[0];
+            context()->ReplaceAllUsesWith(inst->result_id(), replId);
+            iter = context()->KillInst(&*inst);
+          } else {
+            // We've rewritten the operands, so first instruct the def/use
+            // manager to forget uses in the phi before we replace them. After
+            // replacing operands update the def/use manager by re-analyzing
+            // the used ids in this phi.
+            get_def_use_mgr()->EraseUseRecordsOfOperandIds(inst);
+            inst->ReplaceOperands(operands);
+            get_def_use_mgr()->AnalyzeInstUse(inst);
+            ++iter;
           }
+        } else {
+          ++iter;
         }
-        ++icnt;
-      });
-      // If just one live predecessor, replace resultid with live value id.
-      uint32_t replId;
-      if (lcnt == 1) {
-        replId = inst->GetSingleWordInOperand(lidx);
-      } else {
-        // Otherwise create new phi eliminating dead predecessor entries
-        assert(lcnt > 1);
-        replId = TakeNextId();
-        std::vector<ir::Operand> phi_in_opnds;
-        icnt = 0;
-        uint32_t lastId;
-        inst->ForEachInId(
-            [&deadPreds, &icnt, &phi_in_opnds, &lastId, this](uint32_t* idp) {
-              if (icnt % 2 == 1) {
-                if (deadPreds.find(cfg()->block(*idp)) == deadPreds.end()) {
-                  phi_in_opnds.push_back(
-                      {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {lastId}});
-                  phi_in_opnds.push_back(
-                      {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {*idp}});
-                }
-              } else {
-                lastId = *idp;
-              }
-              ++icnt;
-            });
-        std::unique_ptr<ir::Instruction> newPhi(new ir::Instruction(
-            context(), SpvOpPhi, inst->type_id(), replId, phi_in_opnds));
-        get_def_use_mgr()->AnalyzeInstDefUse(&*newPhi);
-        inst->InsertBefore(std::move(newPhi));
       }
-      const uint32_t phiId = inst->result_id();
-      context()->KillNamesAndDecorates(phiId);
-      (void)context()->ReplaceAllUsesWith(phiId, replId);
-      inst = context()->KillInst(inst);
     }
   }
 
-  // Erase dead blocks
-  for (auto ebi = func->begin(); ebi != func->end();)
-    if (elimBlocks.find(&*ebi) != elimBlocks.end())
+  return modified;
+}
+
+bool DeadBranchElimPass::EraseDeadBlocks(
+    ir::Function* func, const std::unordered_set<ir::BasicBlock*>& live_blocks,
+    const std::unordered_set<ir::BasicBlock*>& unreachable_merges,
+    const std::unordered_map<ir::BasicBlock*, ir::BasicBlock*>&
+        unreachable_continues) {
+  bool modified = false;
+  for (auto ebi = func->begin(); ebi != func->end();) {
+    if (unreachable_merges.count(&*ebi)) {
+      // Make unreachable, but leave the label.
+      KillAllInsts(&*ebi, false);
+      // Add unreachable terminator.
+      ebi->AddInstruction(
+          MakeUnique<ir::Instruction>(context(), SpvOpUnreachable, 0, 0,
+                                      std::initializer_list<ir::Operand>{}));
+      ++ebi;
+      modified = true;
+    } else if (unreachable_continues.count(&*ebi)) {
+      // Make unreachable, but leave the label.
+      KillAllInsts(&*ebi, false);
+      // Add unconditional branch to header.
+      assert(unreachable_continues.count(&*ebi));
+      uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id();
+      ebi->AddInstruction(
+          MakeUnique<ir::Instruction>(context(), SpvOpBranch, 0, 0,
+                                      std::initializer_list<ir::Operand>{
+                                          {SPV_OPERAND_TYPE_ID, {cont_id}}}));
+      get_def_use_mgr()->AnalyzeInstUse(&*ebi->tail());
+      ++ebi;
+      modified = true;
+    } else if (!live_blocks.count(&*ebi)) {
+      // Kill this block.
+      KillAllInsts(&*ebi);
       ebi = ebi.Erase();
-    else
+      modified = true;
+    } else {
       ++ebi;
+    }
+  }
+
+  return modified;
+}
+
+bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) {
+  bool modified = false;
+  std::unordered_set<ir::BasicBlock*> live_blocks;
+  modified |= MarkLiveBlocks(func, &live_blocks);
+
+  std::unordered_set<ir::BasicBlock*> unreachable_merges;
+  std::unordered_map<ir::BasicBlock*, ir::BasicBlock*> unreachable_continues;
+  MarkUnreachableStructuredTargets(live_blocks, &unreachable_merges,
+                                   &unreachable_continues);
+  modified |= FixPhiNodesInLiveBlocks(func, live_blocks, unreachable_continues);
+  modified |= EraseDeadBlocks(func, live_blocks, unreachable_merges,
+                              unreachable_continues);
+
   return modified;
 }
 
@@ -349,10 +356,6 @@ bool DeadBranchElimPass::AllExtensionsSupported() const {
 }
 
 Pass::Status DeadBranchElimPass::ProcessImpl() {
-  // Current functionality assumes structured control flow.
-  // TODO(greg-lunarg): Handle non-structured control-flow.
-  if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
-    return Status::SuccessWithoutChange;
   // Do not process if module contains OpGroupDecorate. Additional
   // support required in KillNamesAndDecorates().
   // TODO(greg-lunarg): Add support for OpGroupDecorate
@@ -364,7 +367,7 @@ Pass::Status DeadBranchElimPass::ProcessImpl() {
   ProcessFunction pfn = [this](ir::Function* fp) {
     return EliminateDeadBranches(fp);
   };
-  bool modified = ProcessEntryPointCallTree(pfn, get_module());
+  bool modified = ProcessReachableCallTree(pfn, context());
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
index cdf2e96..2359c53 100644 (file)
@@ -37,9 +37,6 @@ class DeadBranchElimPass : public MemPass {
   using cbb_ptr = const ir::BasicBlock*;
 
  public:
-  using GetBlocksFunction =
-      std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
-
   DeadBranchElimPass();
   const char* name() const override { return "eliminate-dead-branches"; }
   Status Process(ir::IRContext* context) override;
@@ -60,34 +57,77 @@ class DeadBranchElimPass : public MemPass {
   // Add branch to |labelId| to end of block |bp|.
   void AddBranch(uint32_t labelId, ir::BasicBlock* bp);
 
-  // Add selction merge of |labelId| to end of block |bp|.
-  void AddSelectionMerge(uint32_t labelId, ir::BasicBlock* bp);
-
-  // Add conditional branch of |condId|, |trueLabId| and |falseLabId| to end
-  // of block |bp|.
-  void AddBranchConditional(uint32_t condId, uint32_t trueLabId,
-                            uint32_t falseLabId, ir::BasicBlock* bp);
-
-  // If block |bp| contains conditional branch or switch preceeded by an
-  // OpSelctionMerge, return true and return branch and merge instructions
-  // in |branchInst| and |mergeInst| and the conditional id in |condId|.
-  bool GetSelectionBranch(ir::BasicBlock* bp, ir::Instruction** branchInst,
-                          ir::Instruction** mergeInst, uint32_t* condId);
-
-  // Return true if |labelId| has any non-phi, non-backedge references
-  bool HasNonPhiNonBackedgeRef(uint32_t labelId);
-
-  // Compute backedges for blocks in |structuredOrder|.
-  void ComputeBackEdges(std::list<ir::BasicBlock*>& structuredOrder);
-
   // For function |func|, look for BranchConditionals with constant condition
   // and convert to a Branch to the indicated label. Delete resulting dead
-  // blocks. Assumes only structured control flow in shader. Note some such
-  // branches and blocks may be left to avoid creating invalid control flow.
-  // TODO(greg-lunarg): Remove remaining constant conditional branches and
-  // dead blocks.
+  // blocks. Note some such branches and blocks may be left to avoid creating
+  // invalid control flow.
+  // TODO(greg-lunarg): Remove remaining constant conditional branches and dead
+  // blocks.
   bool EliminateDeadBranches(ir::Function* func);
 
+  // Returns the basic block containing |id|.
+  // Note: this pass only requires correct instruction block mappings for the
+  // input. This pass does not preserve the block mapping, so it is not kept
+  // up-to-date during processing.
+  ir::BasicBlock* GetParentBlock(uint32_t id);
+
+  // Marks live blocks reachable from the entry of |func|. Simplifies constant
+  // branches and switches as it proceeds, to limit the number of live blocks.
+  // It is careful not to eliminate backedges even if they are dead, but the
+  // header is live. Likewise, unreachable merge blocks named in live merge
+  // instruction must be retained (though they may be clobbered).
+  bool MarkLiveBlocks(ir::Function* func,
+                      std::unordered_set<ir::BasicBlock*>* live_blocks);
+
+  // Checks for unreachable merge and continue blocks with live headers; those
+  // blocks must be retained. Continues are tracked separately so that a live
+  // phi can be updated to take an undef value from any of its predecessors
+  // that are unreachable continues.
+  //
+  // |unreachable_continues| maps the id of an unreachable continue target to
+  // the header block that declares it.
+  void MarkUnreachableStructuredTargets(
+      const std::unordered_set<ir::BasicBlock*>& live_blocks,
+      std::unordered_set<ir::BasicBlock*>* unreachable_merges,
+      std::unordered_map<ir::BasicBlock*, ir::BasicBlock*>*
+          unreachable_continues);
+
+  // Fix phis in reachable blocks so that only live (or unremovable) incoming
+  // edges are present. If the block now only has a single live incoming edge,
+  // remove the phi and replace its uses with its data input. If the single
+  // remaining incoming edge is from the phi itself, the the phi is in an
+  // unreachable single block loop. Either the block is dead and will be
+  // removed, or it's reachable from an unreachable continue target. In the
+  // latter case that continue target block will be collapsed into a block that
+  // only branches back to its header and we'll eliminate the block with the
+  // phi.
+  //
+  // |unreachable_continues| maps continue targets that cannot be reached to
+  // merge instruction that declares them.
+  bool FixPhiNodesInLiveBlocks(
+      ir::Function* func,
+      const std::unordered_set<ir::BasicBlock*>& live_blocks,
+      const std::unordered_map<ir::BasicBlock*, ir::BasicBlock*>&
+          unreachable_continues);
+
+  // Erases dead blocks. Any block captured in |unreachable_merges| or
+  // |unreachable_continues| is a dead block that is required to remain due to
+  // a live merge instruction in the corresponding header. These blocks will
+  // have their instructions clobbered and will become a label and terminator.
+  // Unreachable merge blocks are terminated by OpUnreachable, while
+  // unreachable continue blocks are terminated by an unconditional branch to
+  // the header. Otherwise, blocks are dead if not explicitly captured in
+  // |live_blocks| and are totally removed.
+  //
+  // |unreachable_continues| maps continue targets that cannot be reached to
+  // corresponding header block that declares them.
+  bool EraseDeadBlocks(
+      ir::Function* func,
+      const std::unordered_set<ir::BasicBlock*>& live_blocks,
+      const std::unordered_set<ir::BasicBlock*>& unreachable_merges,
+      const std::unordered_map<ir::BasicBlock*, ir::BasicBlock*>&
+          unreachable_continues);
+
   // Initialize extensions whitelist
   void InitExtensions();
 
@@ -97,9 +137,6 @@ class DeadBranchElimPass : public MemPass {
   void Initialize(ir::IRContext* c);
   Pass::Status ProcessImpl();
 
-  // All backedge branches in current function
-  std::unordered_set<ir::Instruction*> backedges_;
-
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
 };
index 88bd4bc..b389052 100644 (file)
@@ -138,8 +138,12 @@ bool MemPass::HasOnlyNamesAndDecorates(uint32_t id) const {
   return hasOnlyNamesAndDecorates;
 }
 
-void MemPass::KillAllInsts(ir::BasicBlock* bp) {
-  bp->ForEachInst([this](ir::Instruction* ip) { context()->KillInst(ip); });
+void MemPass::KillAllInsts(ir::BasicBlock* bp, bool killLabel) {
+  bp->ForEachInst([this, killLabel](ir::Instruction* ip) {
+    if (killLabel || ip->opcode() != SpvOpLabel) {
+      context()->KillInst(ip);
+    }
+  });
 }
 
 bool MemPass::HasLoads(uint32_t varId) const {
index 17a9428..1c764f7 100644 (file)
@@ -70,8 +70,9 @@ class MemPass : public Pass {
   // Return true if all uses of |id| are only name or decorate ops.
   bool HasOnlyNamesAndDecorates(uint32_t id) const;
 
-  // Kill all instructions in block |bp|.
-  void KillAllInsts(ir::BasicBlock* bp);
+  // Kill all instructions in block |bp|. Whether or not to kill the label is
+  // indicated by |killLabel|.
+  void KillAllInsts(ir::BasicBlock* bp, bool killLabel = true);
 
   // Return true if any instruction loads from |varId|
   bool HasLoads(uint32_t varId) const;
index 148c546..bb165ae 100644 (file)
@@ -1334,7 +1334,7 @@ OpBranch %33
 OpStore %oc %19
 OpBranch %28
 %29 = OpLabel
-OpBranchConditional %false %27 %28
+OpBranch %27
 %28 = OpLabel
 %35 = OpLoad %v4float %oc
 OpStore %OutColor %35
@@ -1346,6 +1346,569 @@ OpFunctionEnd
                                                  predefs + after, true, true);
 }
 
+#ifdef SPIRV_EFFCEE
+TEST_F(DeadBranchElimTest, LeaveContinueBackedge) {
+  const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
+; CHECK: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
+; CHECK-NEXT: [[merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%void = OpTypeVoid
+%funcTy = OpTypeFunction %void
+%func = OpFunction %void None %funcTy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %4
+%4 = OpLabel
+; Be careful we don't remove the backedge to %2 despite never taking it.
+OpBranchConditional %false %2 %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+TEST_F(DeadBranchElimTest, LeaveContinueBackedgeExtraBlock) {
+  const std::string text = R"(
+; CHECK: OpBranch [[header:%\w+]]
+; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
+; CHECK-NEXT: OpBranch [[continue]]
+; CHECK-NEXT: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[extra:%\w+]] [[merge]]
+; CHECK-NEXT: [[extra]] = OpLabel
+; CHECK-NEXT: OpBranch [[header]]
+; CHECK-NEXT: [[merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%void = OpTypeVoid
+%funcTy = OpTypeFunction %void
+%func = OpFunction %void None %funcTy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %4
+%4 = OpLabel
+; Be careful we don't remove the backedge to %2 despite never taking it.
+OpBranchConditional %false %5 %3
+; This block remains live despite being unreachable.
+%5 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, UnreachableLoopMergeAndContinueTargets) {
+  const std::string text = R"(
+; CHECK: [[undef:%\w+]] = OpUndef %bool
+; CHECK: [[entry:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+; CHECK: OpPhi %bool %false [[entry]] [[undef]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK-NEXT: OpBranch [[ret:%\w+]]
+; CHECK-NEXT: [[ret]] = OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranch [[header]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpUnreachable
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+%funcTy = OpTypeFunction %void
+%func = OpFunction %void None %funcTy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %bool %false %1 %true %continue
+OpLoopMerge %merge %continue None
+OpBranch %3
+%3 = OpLabel
+OpReturn
+%continue = OpLabel
+OpBranch %2
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, EarlyReconvergence) {
+  const std::string text = R"(
+; CHECK-NOT: OpBranchConditional
+; CHECK: [[logical:%\w+]] = OpLogicalOr
+; CHECK-NOT: OpPhi
+; CHECK: OpLogicalAnd {{%\w+}} {{%\w+}} [[logical]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpSelectionMerge %2 None
+OpBranchConditional %false %3 %4
+%3 = OpLabel
+%12 = OpLogicalNot %bool %true
+OpBranch %2
+%4 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %false %5 %6
+%5 = OpLabel
+%10 = OpLogicalAnd %bool %true %false
+OpBranch %7
+%6 = OpLabel
+%11 = OpLogicalOr %bool %true %false
+OpBranch %7
+%7 = OpLabel
+; This phi is in a block preceeding the merge %14!
+%8 = OpPhi %bool %10 %5 %11 %6
+OpBranch %14
+%14 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%9 = OpPhi %bool %12 %3 %8 %14
+%13 = OpLogicalAnd %bool %true %9
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksFloating) {
+  const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%1 = OpTypeFunction %void
+%func = OpFunction %void None %1
+%2 = OpLabel
+OpReturn
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksFloatingJoin) {
+  const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%1 = OpTypeFunction %void %bool
+%func = OpFunction %void None %1
+%bool_param = OpFunctionParameter %bool
+%2 = OpLabel
+OpReturn
+%3 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %bool_param %4 %5
+%4 = OpLabel
+OpBranch %6
+%5 = OpLabel
+OpBranch %6
+%6 = OpLabel
+%7 = OpPhi %bool %true %4 %false %6
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksDeadPhi) {
+  const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpLogicalNot %bool %true
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%1 = OpTypeFunction %void %bool
+%func = OpFunction %void None %1
+%bool_param = OpFunctionParameter %bool
+%2 = OpLabel
+OpBranch %3
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+%5 = OpPhi %bool %true %2 %false %4
+%6 = OpLogicalNot %bool %5
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksPartiallyDeadPhi) {
+  const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: [[param:%\w+]] = OpFunctionParameter
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpBranchConditional [[param]] [[merge:%\w+]] [[br:%\w+]]
+; CHECK-NEXT: [[br]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK-NEXT: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool %true %2 %false [[br]]
+; CHECK-NEXT: OpLogicalNot %bool [[phi]]
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%1 = OpTypeFunction %void %bool
+%func = OpFunction %void None %1
+%bool_param = OpFunctionParameter %bool
+%2 = OpLabel
+OpBranchConditional %bool_param %3 %7
+%7 = OpLabel
+OpBranch %3
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+%5 = OpPhi %bool %true %2 %false %7 %false %4
+%6 = OpLogicalNot %bool %5
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, LiveHeaderDeadPhi) {
+  const std::string text = R"(
+; CHECK: OpLabel
+; CHECK-NOT: OpBranchConditional
+; CHECK-NOT: OpPhi
+; CHECK: OpLogicalNot %bool %false
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpBranchConditional %true %2 %3
+%2 = OpLabel
+OpBranch %3
+%3 = OpLabel
+%5 = OpPhi %bool %true %3 %false %2
+%6 = OpLogicalNot %bool %5
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, ExtraBackedgeBlocksLive) {
+  const std::string text = R"(
+; CHECK: [[entry:%\w+]] = OpLabel
+; CHECK-NOT: OpSelectionMerge
+; CHECK: OpBranch [[header:%\w+]]
+; CHECK-NEXT: [[header]] = OpLabel
+; CHECK-NEXT: OpPhi %bool %true [[entry]] %false [[backedge:%\w+]]
+; CHECK-NEXT: OpLoopMerge
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%func_ty = OpTypeFunction %void %bool
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %bool
+%entry = OpLabel
+OpSelectionMerge %if_merge None
+; This dead branch is included to ensure the pass does work.
+OpBranchConditional %false %if_merge %loop_header
+%loop_header = OpLabel
+; Both incoming edges are live, so the phi should be untouched.
+%phi = OpPhi %bool %true %entry %false %backedge
+OpLoopMerge %loop_merge %continue None
+OpBranchConditional %param %loop_merge %continue
+%continue = OpLabel
+OpBranch %backedge
+%backedge = OpLabel
+OpBranch %loop_header
+%loop_merge = OpLabel
+OpBranch %if_merge
+%if_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, ExtraBackedgeBlocksUnreachable) {
+  const std::string text = R"(
+; CHECK: [[undef:%\w+]] = OpUndef
+; CHECK: [[entry:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+; CHECK-NEXT: [[header]] = OpLabel
+; CHECK-NEXT: OpPhi %bool %true [[entry]] [[undef]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK-NEXT: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranch [[header]]
+; CHECK-NEXT: [[merge]] = OpLabel
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%func_ty = OpTypeFunction %void %bool
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %bool
+%entry = OpLabel
+OpBranch %loop_header
+%loop_header = OpLabel
+; Since the continue is unreachable, %backedge will be removed. The phi will
+; instead require an edge from %continue.
+%phi = OpPhi %bool %true %entry %false %backedge
+OpLoopMerge %merge %continue None
+OpBranch %merge
+%continue = OpLabel
+OpBranch %backedge
+%backedge = OpLabel
+OpBranch %loop_header
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, ExtraBackedgePartiallyDead) {
+  const std::string text = R"(
+; CHECK: OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
+; CHECK: [[continue]] = OpLabel
+; CHECK: OpBranch [[extra:%\w+]]
+; CHECK: [[extra]] = OpLabel
+; CHECK-NOT: OpSelectionMerge
+; CHECK-NEXT: OpBranch [[else:%\w+]]
+; CHECK-NEXT: [[else]] = OpLabel
+; CHECK-NEXT: OpLogicalOr
+; CHECK-NEXT: OpBranch [[backedge:%\w+]]
+; CHECK-NEXT: [[backedge:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[header]]
+; CHECK-NEXT: [[merge]] = OpLabel
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpName %func "func"
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%func_ty = OpTypeFunction %void %bool
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %bool
+%entry = OpLabel
+OpBranch %loop_header
+%loop_header = OpLabel
+OpLoopMerge %loop_merge %continue None
+OpBranchConditional %param %loop_merge %continue
+%continue = OpLabel
+OpBranch %extra
+%extra = OpLabel
+OpSelectionMerge %backedge None
+OpBranchConditional %false %then %else
+%then = OpLabel
+%and = OpLogicalAnd %bool %true %false
+OpBranch %backedge
+%else = OpLabel
+%or = OpLogicalOr %bool %true %false
+OpBranch %backedge
+%backedge = OpLabel
+OpBranch %loop_header
+%loop_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, UnreachableContinuePhiInMerge) {
+  const std::string text = R"(
+; CHECK: [[float_undef:%\w+]] = OpUndef %float
+; CHECK: [[int_undef:%\w+]] = OpUndef %int
+; CHECK: [[entry:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+; CHECK-NEXT: [[header]] = OpLabel
+; CHECK-NEXT: OpPhi %float {{%\w+}} [[entry]] [[float_undef]] [[continue:%\w+]]
+; CHECK-NEXT: OpPhi %int {{%\w+}} [[entry]] [[int_undef]] [[continue]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: [[fadd:%\w+]] = OpFAdd
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK-NEXT: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranch [[header]]
+; CHECK-NEXT: [[merge]] = OpLabel
+; CHECK-NEXT: OpStore {{%\w+}} [[fadd]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %o
+               OpExecutionMode %main OriginUpperLeft
+               OpSource GLSL 430
+               OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+               OpSourceExtension "GL_GOOGLE_include_directive"
+               OpName %main "main"
+               OpName %o "o"
+               OpName %S "S"
+               OpMemberName %S 0 "a"
+               OpName %U_t "U_t"
+               OpMemberName %U_t 0 "g_F"
+               OpMemberName %U_t 1 "g_F2"
+               OpDecorate %o Location 0
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %U_t 0 Volatile
+               OpMemberDecorate %U_t 0 Offset 0
+               OpMemberDecorate %U_t 1 Offset 4
+               OpDecorate %U_t BufferBlock
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+    %float_0 = OpConstant %float 0
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+      %int_0 = OpConstant %int 0
+     %int_10 = OpConstant %int 10
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+    %float_1 = OpConstant %float 1
+    %float_5 = OpConstant %float 5
+      %int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+          %o = OpVariable %_ptr_Output_float Output
+          %S = OpTypeStruct %float
+        %U_t = OpTypeStruct %S %S
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+       %main = OpFunction %void None %7
+         %22 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %24 = OpPhi %float %float_0 %22 %25 %26
+         %27 = OpPhi %int %int_0 %22 %28 %26
+               OpLoopMerge %29 %26 None
+               OpBranch %40
+         %40 = OpLabel
+         %25 = OpFAdd %float %24 %float_1
+               OpSelectionMerge %30 None
+               OpBranchConditional %true %31 %30
+         %31 = OpLabel
+               OpBranch %29
+         %30 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+         %28 = OpIAdd %int %27 %int_1
+         %32 = OpSLessThan %bool %27 %int_10
+; continue block branches to the header or another none dead block.
+               OpBranchConditional %32 %23 %29
+         %29 = OpLabel
+         %33 = OpPhi %float %24 %26 %25 %31
+               OpStore %o %33
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::DeadBranchElimPass>(text, true);
+}
+#endif
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow