Use structural dominance to validate cfg (#4832)

* Structural dominance introduced in SPIR-V 1.6 rev2
* Changes the structured cfg validation to use structural dominance
  * structural dominance is based on a cfg where merge and continue
    declarations are counted as graph edges
* Basic blocks now track structural predecessors and structural
  successors
* Add validation for entry into a loop
* Fixed an issue with inlining a single block loop
  * The continue target needs to be moved to the latch block
* Simplify the calculation of structured exits
  * no longer requires block depth
* Update many invalid tests
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index 2cc3125..6e73f1c 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -508,6 +508,37 @@
   delete &*loop_merge_itr;
 }
 
+void InlinePass::UpdateSingleBlockLoopContinueTarget(
+    uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  auto& header = new_blocks->front();
+  auto* merge_inst = header->GetLoopMergeInst();
+
+  // The back-edge block is split at the branch to create a new back-edge
+  // block. The old block is modified to branch to the new block. The loop
+  // merge instruction is updated to declare the new block as the continue
+  // target. This has the effect of changing the loop from being a large
+  // continue construct and an empty loop construct to being a loop with a loop
+  // construct and a trivial continue construct. This change is made to satisfy
+  // structural dominance.
+
+  // Add the new basic block.
+  std::unique_ptr<BasicBlock> new_block =
+      MakeUnique<BasicBlock>(NewLabel(new_id));
+  auto& old_backedge = new_blocks->back();
+  auto old_branch = old_backedge->tail();
+
+  // Move the old back edge into the new block.
+  std::unique_ptr<Instruction> br(&*old_branch);
+  new_block->AddInstruction(std::move(br));
+
+  // Add a branch to the new block from the old back-edge block.
+  AddBranch(new_id, &old_backedge);
+  new_blocks->push_back(std::move(new_block));
+
+  // Update the loop's continue target to the new block.
+  merge_inst->SetInOperand(1u, {new_id});
+}
+
 bool InlinePass::GenInlineCode(
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks,
     std::vector<std::unique_ptr<Instruction>>* new_vars,
@@ -639,9 +670,19 @@
   // Finalize inline code.
   new_blocks->push_back(std::move(new_blk_ptr));
 
-  if (caller_is_loop_header && (new_blocks->size() > 1))
+  if (caller_is_loop_header && (new_blocks->size() > 1)) {
     MoveLoopMergeInstToFirstBlock(new_blocks);
 
+    // If the loop was a single basic block previously, update it's structure.
+    auto& header = new_blocks->front();
+    auto* merge_inst = header->GetLoopMergeInst();
+    if (merge_inst->GetSingleWordInOperand(1u) == header->id()) {
+      auto new_id = context()->TakeNextId();
+      if (new_id == 0) return false;
+      UpdateSingleBlockLoopContinueTarget(new_id, new_blocks);
+    }
+  }
+
   // Update block map given replacement blocks.
   for (auto& blk : *new_blocks) {
     id2block_[blk->id()] = &*blk;
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index 9a5429b..f204395 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -235,6 +235,12 @@
   // Move the OpLoopMerge from the last block back to the first.
   void MoveLoopMergeInstToFirstBlock(
       std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
+  // Update the structure of single block loops so that the inlined code ends
+  // up in the loop construct and a new continue target is added to satisfy
+  // structural dominance.
+  void UpdateSingleBlockLoopContinueTarget(
+      uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 };
 
 }  // namespace opt
diff --git a/source/val/basic_block.cpp b/source/val/basic_block.cpp
index b2a8793..a489ae0 100644
--- a/source/val/basic_block.cpp
+++ b/source/val/basic_block.cpp
@@ -24,7 +24,8 @@
 BasicBlock::BasicBlock(uint32_t label_id)
     : id_(label_id),
       immediate_dominator_(nullptr),
-      immediate_post_dominator_(nullptr),
+      immediate_structural_dominator_(nullptr),
+      immediate_structural_post_dominator_(nullptr),
       predecessors_(),
       successors_(),
       type_(0),
@@ -36,21 +37,32 @@
   immediate_dominator_ = dom_block;
 }
 
-void BasicBlock::SetImmediatePostDominator(BasicBlock* pdom_block) {
-  immediate_post_dominator_ = pdom_block;
+void BasicBlock::SetImmediateStructuralDominator(BasicBlock* dom_block) {
+  immediate_structural_dominator_ = dom_block;
+}
+
+void BasicBlock::SetImmediateStructuralPostDominator(BasicBlock* pdom_block) {
+  immediate_structural_post_dominator_ = pdom_block;
 }
 
 const BasicBlock* BasicBlock::immediate_dominator() const {
   return immediate_dominator_;
 }
 
-const BasicBlock* BasicBlock::immediate_post_dominator() const {
-  return immediate_post_dominator_;
+const BasicBlock* BasicBlock::immediate_structural_dominator() const {
+  return immediate_structural_dominator_;
+}
+
+const BasicBlock* BasicBlock::immediate_structural_post_dominator() const {
+  return immediate_structural_post_dominator_;
 }
 
 BasicBlock* BasicBlock::immediate_dominator() { return immediate_dominator_; }
-BasicBlock* BasicBlock::immediate_post_dominator() {
-  return immediate_post_dominator_;
+BasicBlock* BasicBlock::immediate_structural_dominator() {
+  return immediate_structural_dominator_;
+}
+BasicBlock* BasicBlock::immediate_structural_post_dominator() {
+  return immediate_structural_post_dominator_;
 }
 
 void BasicBlock::RegisterSuccessors(
@@ -58,6 +70,10 @@
   for (auto& block : next_blocks) {
     block->predecessors_.push_back(this);
     successors_.push_back(block);
+
+    // Register structural successors/predecessors too.
+    block->structural_predecessors_.push_back(this);
+    structural_successors_.push_back(block);
   }
 }
 
@@ -67,10 +83,16 @@
            std::find(other.dom_begin(), other.dom_end(), this));
 }
 
-bool BasicBlock::postdominates(const BasicBlock& other) const {
-  return (this == &other) ||
-         !(other.pdom_end() ==
-           std::find(other.pdom_begin(), other.pdom_end(), this));
+bool BasicBlock::structurally_dominates(const BasicBlock& other) const {
+  return (this == &other) || !(other.structural_dom_end() ==
+                               std::find(other.structural_dom_begin(),
+                                         other.structural_dom_end(), this));
+}
+
+bool BasicBlock::structurally_postdominates(const BasicBlock& other) const {
+  return (this == &other) || !(other.structural_pdom_end() ==
+                               std::find(other.structural_pdom_begin(),
+                                         other.structural_pdom_end(), this));
 }
 
 BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {}
@@ -107,21 +129,43 @@
   return DominatorIterator();
 }
 
-const BasicBlock::DominatorIterator BasicBlock::pdom_begin() const {
-  return DominatorIterator(
-      this, [](const BasicBlock* b) { return b->immediate_post_dominator(); });
+const BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() const {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_dominator();
+  });
 }
 
-BasicBlock::DominatorIterator BasicBlock::pdom_begin() {
-  return DominatorIterator(
-      this, [](const BasicBlock* b) { return b->immediate_post_dominator(); });
+BasicBlock::DominatorIterator BasicBlock::structural_dom_begin() {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_dominator();
+  });
 }
 
-const BasicBlock::DominatorIterator BasicBlock::pdom_end() const {
+const BasicBlock::DominatorIterator BasicBlock::structural_dom_end() const {
   return DominatorIterator();
 }
 
-BasicBlock::DominatorIterator BasicBlock::pdom_end() {
+BasicBlock::DominatorIterator BasicBlock::structural_dom_end() {
+  return DominatorIterator();
+}
+
+const BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() const {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_post_dominator();
+  });
+}
+
+BasicBlock::DominatorIterator BasicBlock::structural_pdom_begin() {
+  return DominatorIterator(this, [](const BasicBlock* b) {
+    return b->immediate_structural_post_dominator();
+  });
+}
+
+const BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() const {
+  return DominatorIterator();
+}
+
+BasicBlock::DominatorIterator BasicBlock::structural_pdom_end() {
   return DominatorIterator();
 }
 
diff --git a/source/val/basic_block.h b/source/val/basic_block.h
index 47cd06d..f778aa0 100644
--- a/source/val/basic_block.h
+++ b/source/val/basic_block.h
@@ -64,7 +64,27 @@
   /// Returns the successors of the BasicBlock
   std::vector<BasicBlock*>* successors() { return &successors_; }
 
-  /// Returns true if the block is reachable in the CFG
+  /// Returns the structural successors of the BasicBlock
+  std::vector<BasicBlock*>* structural_predecessors() {
+    return &structural_predecessors_;
+  }
+
+  /// Returns the structural predecessors of the BasicBlock
+  const std::vector<BasicBlock*>* structural_predecessors() const {
+    return &structural_predecessors_;
+  }
+
+  /// Returns the structural successors of the BasicBlock
+  std::vector<BasicBlock*>* structural_successors() {
+    return &structural_successors_;
+  }
+
+  /// Returns the structural predecessors of the BasicBlock
+  const std::vector<BasicBlock*>* structural_successors() const {
+    return &structural_successors_;
+  }
+
+  /// Returns true if the block is structurally reachable in the CFG.
   bool reachable() const { return reachable_; }
 
   /// Returns true if BasicBlock is of the given type
@@ -89,10 +109,15 @@
   /// @param[in] dom_block The dominator block
   void SetImmediateDominator(BasicBlock* dom_block);
 
+  /// Sets the immediate dominator of this basic block
+  ///
+  /// @param[in] dom_block The dominator block
+  void SetImmediateStructuralDominator(BasicBlock* dom_block);
+
   /// Sets the immediate post dominator of this basic block
   ///
   /// @param[in] pdom_block The post dominator block
-  void SetImmediatePostDominator(BasicBlock* pdom_block);
+  void SetImmediateStructuralPostDominator(BasicBlock* pdom_block);
 
   /// Returns the immediate dominator of this basic block
   BasicBlock* immediate_dominator();
@@ -100,11 +125,17 @@
   /// Returns the immediate dominator of this basic block
   const BasicBlock* immediate_dominator() const;
 
-  /// Returns the immediate post dominator of this basic block
-  BasicBlock* immediate_post_dominator();
+  /// Returns the immediate dominator of this basic block
+  BasicBlock* immediate_structural_dominator();
+
+  /// Returns the immediate dominator of this basic block
+  const BasicBlock* immediate_structural_dominator() const;
 
   /// Returns the immediate post dominator of this basic block
-  const BasicBlock* immediate_post_dominator() const;
+  BasicBlock* immediate_structural_post_dominator();
+
+  /// Returns the immediate post dominator of this basic block
+  const BasicBlock* immediate_structural_post_dominator() const;
 
   /// Returns the label instruction for the block, or nullptr if not set.
   const Instruction* label() const { return label_; }
@@ -132,9 +163,18 @@
   /// Assumes dominators have been computed.
   bool dominates(const BasicBlock& other) const;
 
-  /// Returns true if this block postdominates the other block.
-  /// Assumes dominators have been computed.
-  bool postdominates(const BasicBlock& other) const;
+  /// Returns true if this block structurally dominates the other block.
+  /// Assumes structural dominators have been computed.
+  bool structurally_dominates(const BasicBlock& other) const;
+
+  /// Returns true if this block structurally postdominates the other block.
+  /// Assumes structural dominators have been computed.
+  bool structurally_postdominates(const BasicBlock& other) const;
+
+  void RegisterStructuralSuccessor(BasicBlock* block) {
+    block->structural_predecessors_.push_back(this);
+    structural_successors_.push_back(block);
+  }
 
   /// @brief A BasicBlock dominator iterator class
   ///
@@ -191,18 +231,32 @@
   /// block
   DominatorIterator dom_end();
 
+  /// Returns a dominator iterator which points to the current block
+  const DominatorIterator structural_dom_begin() const;
+
+  /// Returns a dominator iterator which points to the current block
+  DominatorIterator structural_dom_begin();
+
+  /// Returns a dominator iterator which points to one element past the first
+  /// block
+  const DominatorIterator structural_dom_end() const;
+
+  /// Returns a dominator iterator which points to one element past the first
+  /// block
+  DominatorIterator structural_dom_end();
+
   /// Returns a post dominator iterator which points to the current block
-  const DominatorIterator pdom_begin() const;
+  const DominatorIterator structural_pdom_begin() const;
   /// Returns a post dominator iterator which points to the current block
-  DominatorIterator pdom_begin();
+  DominatorIterator structural_pdom_begin();
 
   /// Returns a post dominator iterator which points to one element past the
   /// last block
-  const DominatorIterator pdom_end() const;
+  const DominatorIterator structural_pdom_end() const;
 
   /// Returns a post dominator iterator which points to one element past the
   /// last block
-  DominatorIterator pdom_end();
+  DominatorIterator structural_pdom_end();
 
  private:
   /// Id of the BasicBlock
@@ -211,8 +265,11 @@
   /// Pointer to the immediate dominator of the BasicBlock
   BasicBlock* immediate_dominator_;
 
-  /// Pointer to the immediate dominator of the BasicBlock
-  BasicBlock* immediate_post_dominator_;
+  /// Pointer to the immediate structural dominator of the BasicBlock
+  BasicBlock* immediate_structural_dominator_;
+
+  /// Pointer to the immediate structural post dominator of the BasicBlock
+  BasicBlock* immediate_structural_post_dominator_;
 
   /// The set of predecessors of the BasicBlock
   std::vector<BasicBlock*> predecessors_;
@@ -231,6 +288,9 @@
 
   /// Terminator of this block.
   const Instruction* terminator_;
+
+  std::vector<BasicBlock*> structural_predecessors_;
+  std::vector<BasicBlock*> structural_successors_;
 };
 
 /// @brief Returns true if the iterators point to the same element or if both
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 251e2bb..6e367f3 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -70,60 +70,48 @@
 
 void Construct::set_exit(BasicBlock* block) { exit_block_ = block; }
 
-Construct::ConstructBlockSet Construct::blocks(Function* function) const {
-  auto header = entry_block();
-  auto merge = exit_block();
-  assert(header);
-  int header_depth = function->GetBlockDepth(const_cast<BasicBlock*>(header));
-  ConstructBlockSet construct_blocks;
-  std::unordered_set<BasicBlock*> corresponding_headers;
-  for (auto& other : corresponding_constructs()) {
-    // The corresponding header can be the same block as this construct's
-    // header for loops with no loop construct. In those cases, don't add the
-    // loop header as it prevents finding any blocks in the construct.
-    if (type() != ConstructType::kContinue || other->entry_block() != header) {
-      corresponding_headers.insert(other->entry_block());
+Construct::ConstructBlockSet Construct::blocks(Function* /*function*/) const {
+  const auto header = entry_block();
+  const auto exit = exit_block();
+  const bool is_continue = type() == ConstructType::kContinue;
+  const bool is_loop = type() == ConstructType::kLoop;
+  const BasicBlock* continue_header = nullptr;
+  if (is_loop) {
+    // The only corresponding construct for a loop is the continue.
+    for (auto& other : corresponding_constructs()) {
+      continue_header = other->entry_block();
+      break;
     }
   }
   std::vector<BasicBlock*> stack;
   stack.push_back(const_cast<BasicBlock*>(header));
+  ConstructBlockSet construct_blocks;
   while (!stack.empty()) {
-    BasicBlock* block = stack.back();
+    auto* block = stack.back();
     stack.pop_back();
 
-    if (merge == block && ExitBlockIsMergeBlock()) {
-      // Merge block is not part of the construct.
-      continue;
-    }
+    if (header->structurally_dominates(*block)) {
+      bool include = false;
+      if (is_continue && exit->structurally_postdominates(*block)) {
+        // Continue construct include blocks dominated by the continue target
+        // and post-dominated by the back-edge block.
+        include = true;
+      } else if (!exit->structurally_dominates(*block)) {
+        // Selection and loop constructs include blocks dominated by the header
+        // and not dominated by the merge.
+        include = true;
+        if (is_loop && continue_header->structurally_dominates(*block)) {
+          // Loop constructs have an additional constraint that they do not
+          // include blocks dominated by the continue construct. Since all
+          // blocks in the continue construct are dominated by the continue
+          // target, we just test for dominance by continue target.
+          include = false;
+        }
+      }
+      if (include) {
+        if (!construct_blocks.insert(block).second) continue;
 
-    if (corresponding_headers.count(block)) {
-      // Entered a corresponding construct.
-      continue;
-    }
-
-    int block_depth = function->GetBlockDepth(block);
-    if (block_depth < header_depth) {
-      // Broke to outer construct.
-      continue;
-    }
-
-    // In a loop, the continue target is at a depth of the loop construct + 1.
-    // A selection construct nested directly within the loop construct is also
-    // at the same depth. It is valid, however, to branch directly to the
-    // continue target from within the selection construct.
-    if (block != header && block_depth == header_depth &&
-        type() == ConstructType::kSelection &&
-        block->is_type(kBlockTypeContinue)) {
-      // Continued to outer construct.
-      continue;
-    }
-
-    if (!construct_blocks.insert(block).second) continue;
-
-    if (merge != block) {
-      for (auto succ : *block->successors()) {
-        // All blocks in the construct must be dominated by the header.
-        if (header->dominates(*succ)) {
+        for (auto succ : *block->structural_successors()) {
           stack.push_back(succ);
         }
       }
@@ -181,11 +169,12 @@
       for (auto& use : block->label()->uses()) {
         if ((use.first->opcode() == SpvOpLoopMerge ||
              use.first->opcode() == SpvOpSelectionMerge) &&
-            use.second == 1 && use.first->block()->dominates(*block)) {
+            use.second == 1 &&
+            use.first->block()->structurally_dominates(*block)) {
           return use.first->block();
         }
       }
-      return block->immediate_dominator();
+      return block->immediate_structural_dominator();
     };
 
     bool seen_switch = false;
@@ -201,7 +190,7 @@
            terminator->opcode() == SpvOpSwitch)) {
         auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u);
         auto merge_block = merge_inst->function()->GetBlock(merge_target).first;
-        if (merge_block->dominates(*header)) {
+        if (merge_block->structurally_dominates(*header)) {
           block = NextBlock(block);
           continue;
         }
diff --git a/source/val/function.cpp b/source/val/function.cpp
index f3292b0..fc7ccd0 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -73,6 +73,8 @@
   BasicBlock& continue_target_block = blocks_.at(continue_id);
   assert(current_block_ &&
          "RegisterLoopMerge must be called when called within a block");
+  current_block_->RegisterStructuralSuccessor(&merge_block);
+  current_block_->RegisterStructuralSuccessor(&continue_target_block);
 
   current_block_->set_type(kBlockTypeLoop);
   merge_block.set_type(kBlockTypeMerge);
@@ -101,6 +103,7 @@
   current_block_->set_type(kBlockTypeSelection);
   merge_block.set_type(kBlockTypeMerge);
   merge_block_header_[&merge_block] = current_block_;
+  current_block_->RegisterStructuralSuccessor(&merge_block);
 
   AddConstruct({ConstructType::kSelection, current_block(), &merge_block});
 
@@ -251,16 +254,6 @@
   };
 }
 
-Function::GetBlocksFunction
-Function::AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const {
-  return [this](const BasicBlock* block) {
-    auto where = loop_header_successors_plus_continue_target_map_.find(block);
-    return where == loop_header_successors_plus_continue_target_map_.end()
-               ? AugmentedCFGSuccessorsFunction()(block)
-               : &(*where).second;
-  };
-}
-
 Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const {
   return [this](const BasicBlock* block) {
     auto where = augmented_predecessors_map_.find(block);
@@ -269,11 +262,35 @@
   };
 }
 
+Function::GetBlocksFunction Function::AugmentedStructuralCFGSuccessorsFunction()
+    const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_successors_map_.find(block);
+    return where == augmented_successors_map_.end()
+               ? block->structural_successors()
+               : &(*where).second;
+  };
+}
+
+Function::GetBlocksFunction
+Function::AugmentedStructuralCFGPredecessorsFunction() const {
+  return [this](const BasicBlock* block) {
+    auto where = augmented_predecessors_map_.find(block);
+    return where == augmented_predecessors_map_.end()
+               ? block->structural_predecessors()
+               : &(*where).second;
+  };
+}
+
 void Function::ComputeAugmentedCFG() {
   // Compute the successors of the pseudo-entry block, and
   // the predecessors of the pseudo exit block.
-  auto succ_func = [](const BasicBlock* b) { return b->successors(); };
-  auto pred_func = [](const BasicBlock* b) { return b->predecessors(); };
+  auto succ_func = [](const BasicBlock* b) {
+    return b->structural_successors();
+  };
+  auto pred_func = [](const BasicBlock* b) {
+    return b->structural_predecessors();
+  };
   CFA<BasicBlock>::ComputeAugmentedCFG(
       ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_,
       &augmented_successors_map_, &augmented_predecessors_map_, succ_func,
diff --git a/source/val/function.h b/source/val/function.h
index 2fe30bd..126b1dc 100644
--- a/source/val/function.h
+++ b/source/val/function.h
@@ -184,12 +184,12 @@
       std::function<const std::vector<BasicBlock*>*(const BasicBlock*)>;
   /// Returns the block successors function for the augmented CFG.
   GetBlocksFunction AugmentedCFGSuccessorsFunction() const;
-  /// Like AugmentedCFGSuccessorsFunction, but also includes a forward edge from
-  /// a loop header block to its continue target, if they are different blocks.
-  GetBlocksFunction
-  AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const;
   /// Returns the block predecessors function for the augmented CFG.
   GetBlocksFunction AugmentedCFGPredecessorsFunction() const;
+  /// Returns the block structural successors function for the augmented CFG.
+  GetBlocksFunction AugmentedStructuralCFGSuccessorsFunction() const;
+  /// Returns the block structural predecessors function for the augmented CFG.
+  GetBlocksFunction AugmentedStructuralCFGPredecessorsFunction() const;
 
   /// Returns the control flow nesting depth of the given basic block.
   /// This function only works when you have structured control flow.
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index be05c9c..9806462 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -477,7 +477,7 @@
     if (!visited.insert(block).second) continue;
 
     if (target_reachable && block->reachable() &&
-        target_block->dominates(*block)) {
+        target_block->structurally_dominates(*block)) {
       // Still in the case construct.
       for (auto successor : *block->successors()) {
         stack.push_back(successor);
@@ -550,7 +550,7 @@
       const auto target_block = function->GetBlock(target).first;
       // OpSwitch must dominate all its case constructs.
       if (header->reachable() && target_block->reachable() &&
-          !header->dominates(*target_block)) {
+          !header->structurally_dominates(*target_block)) {
         return _.diag(SPV_ERROR_INVALID_CFG, header->label())
                << "Selection header " << _.getIdName(header->id())
                << " does not dominate its case construct "
@@ -723,9 +723,10 @@
   // Check construct rules
   for (const Construct& construct : function->constructs()) {
     auto header = construct.entry_block();
+    if (!header->reachable()) continue;
     auto merge = construct.exit_block();
 
-    if (header->reachable() && !merge) {
+    if (!merge) {
       std::string construct_name, header_name, exit_name;
       std::tie(construct_name, header_name, exit_name) =
           ConstructNames(construct.type());
@@ -735,28 +736,27 @@
                     exit_name + ". This may be a bug in the validator.";
     }
 
-    // If the exit block is reachable then it's dominated by the
-    // header.
-    if (merge && merge->reachable()) {
-      if (!header->dominates(*merge)) {
-        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
-               << ConstructErrorString(construct, _.getIdName(header->id()),
-                                       _.getIdName(merge->id()),
-                                       "does not dominate");
-      }
-      // If it's really a merge block for a selection or loop, then it must be
-      // *strictly* dominated by the header.
-      if (construct.ExitBlockIsMergeBlock() && (header == merge)) {
-        return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
-               << ConstructErrorString(construct, _.getIdName(header->id()),
-                                       _.getIdName(merge->id()),
-                                       "does not strictly dominate");
-      }
+    // If the header is reachable, the merge is guaranteed to be structurally
+    // reachable.
+    if (!header->structurally_dominates(*merge)) {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
+             << ConstructErrorString(construct, _.getIdName(header->id()),
+                                     _.getIdName(merge->id()),
+                                     "does not dominate");
     }
+    // If it's really a merge block for a selection or loop, then it must be
+    // *strictly* dominated by the header.
+    if (construct.ExitBlockIsMergeBlock() && (header == merge)) {
+      return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
+             << ConstructErrorString(construct, _.getIdName(header->id()),
+                                     _.getIdName(merge->id()),
+                                     "does not strictly dominate");
+    }
+
     // Check post-dominance for continue constructs.  But dominance and
     // post-dominance only make sense when the construct is reachable.
-    if (header->reachable() && construct.type() == ConstructType::kContinue) {
-      if (!merge->postdominates(*header)) {
+    if (construct.type() == ConstructType::kContinue) {
+      if (!merge->structurally_postdominates(*header)) {
         return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(merge->id()))
                << ConstructErrorString(construct, _.getIdName(header->id()),
                                        _.getIdName(merge->id()),
@@ -771,7 +771,7 @@
     for (auto block : construct_blocks) {
       // Check that all exits from the construct are via structured exits.
       for (auto succ : *block->successors()) {
-        if (block->reachable() && !construct_blocks.count(succ) &&
+        if (!construct_blocks.count(succ) &&
             !construct.IsStructuredExit(_, succ)) {
           return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
                  << "block <ID> " << _.getIdName(block->id()) << " exits the "
@@ -813,6 +813,43 @@
       }
     }
 
+    if (construct.type() == ConstructType::kLoop) {
+      // If the continue target differs from the loop header, then check that
+      // all edges into the continue construct come from within the loop.
+      const auto index = header->terminator() - &_.ordered_instructions()[0];
+      const auto& merge_inst = _.ordered_instructions()[index - 1];
+      const auto continue_id = merge_inst.GetOperandAs<uint32_t>(1);
+      const auto* continue_inst = _.FindDef(continue_id);
+      // OpLabel instructions aren't stored as part of the basic block for
+      // legacy reaasons. Grab the next instruction and use it's block pointer
+      // instead.
+      const auto next_index =
+          (continue_inst - &_.ordered_instructions()[0]) + 1;
+      const auto& next_inst = _.ordered_instructions()[next_index];
+      const auto* continue_target = next_inst.block();
+      if (header->id() != continue_id) {
+        for (auto pred : *continue_target->predecessors()) {
+          // Ignore back-edges from within the continue construct.
+          bool is_back_edge = false;
+          for (auto back_edge : back_edges) {
+            uint32_t back_edge_block;
+            uint32_t header_block;
+            std::tie(back_edge_block, header_block) = back_edge;
+            if (header_block == continue_id && back_edge_block == pred->id())
+              is_back_edge = true;
+          }
+          if (!construct_blocks.count(pred) && !is_back_edge) {
+            return _.diag(SPV_ERROR_INVALID_CFG, pred->terminator())
+                   << "Block " << _.getIdName(pred->id())
+                   << " branches to the loop continue target "
+                   << _.getIdName(continue_id)
+                   << ", but is not contained in the associated loop construct "
+                   << _.getIdName(header->id());
+          }
+        }
+      }
+    }
+
     // Checks rules for case constructs.
     if (construct.type() == ConstructType::kSelection &&
         header->terminator()->opcode() == SpvOpSwitch) {
@@ -850,15 +887,12 @@
              << _.getIdName(function.id());
     }
 
-    // Set each block's immediate dominator and immediate postdominator,
-    // and find all back-edges.
+    // Set each block's immediate dominator.
     //
     // We want to analyze all the blocks in the function, even in degenerate
     // control flow cases including unreachable blocks.  So use the augmented
     // CFG to ensure we cover all the blocks.
     std::vector<const BasicBlock*> postorder;
-    std::vector<const BasicBlock*> postdom_postorder;
-    std::vector<std::pair<uint32_t, uint32_t>> back_edges;
     auto ignore_block = [](const BasicBlock*) {};
     auto ignore_edge = [](const BasicBlock*, const BasicBlock*) {};
     auto no_terminal_blocks = [](const BasicBlock*) { return false; };
@@ -874,30 +908,7 @@
         if (edge.first != edge.second)
           edge.first->SetImmediateDominator(edge.second);
       }
-
-      /// calculate post dominators
-      CFA<BasicBlock>::DepthFirstTraversal(
-          function.pseudo_exit_block(),
-          function.AugmentedCFGPredecessorsFunction(), ignore_block,
-          [&](const BasicBlock* b) { postdom_postorder.push_back(b); },
-          ignore_edge, no_terminal_blocks);
-      auto postdom_edges = CFA<BasicBlock>::CalculateDominators(
-          postdom_postorder, function.AugmentedCFGSuccessorsFunction());
-      for (auto edge : postdom_edges) {
-        edge.first->SetImmediatePostDominator(edge.second);
-      }
-      /// calculate back edges.
-      CFA<BasicBlock>::DepthFirstTraversal(
-          function.pseudo_entry_block(),
-          function
-              .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(),
-          ignore_block, ignore_block,
-          [&](const BasicBlock* from, const BasicBlock* to) {
-            back_edges.emplace_back(from->id(), to->id());
-          },
-          no_terminal_blocks);
     }
-    UpdateContinueConstructExitBlocks(function, back_edges);
 
     auto& blocks = function.ordered_blocks();
     if (!blocks.empty()) {
@@ -931,6 +942,52 @@
 
     /// Structured control flow checks are only required for shader capabilities
     if (_.HasCapability(SpvCapabilityShader)) {
+      // Calculate structural dominance.
+      postorder.clear();
+      std::vector<const BasicBlock*> postdom_postorder;
+      std::vector<std::pair<uint32_t, uint32_t>> back_edges;
+      if (!function.ordered_blocks().empty()) {
+        /// calculate dominators
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.first_block(),
+            function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block,
+            [&](const BasicBlock* b) { postorder.push_back(b); }, ignore_edge,
+            no_terminal_blocks);
+        auto edges = CFA<BasicBlock>::CalculateDominators(
+            postorder, function.AugmentedStructuralCFGPredecessorsFunction());
+        for (auto edge : edges) {
+          if (edge.first != edge.second)
+            edge.first->SetImmediateStructuralDominator(edge.second);
+        }
+
+        /// calculate post dominators
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.pseudo_exit_block(),
+            function.AugmentedStructuralCFGPredecessorsFunction(), ignore_block,
+            [&](const BasicBlock* b) { postdom_postorder.push_back(b); },
+            ignore_edge, no_terminal_blocks);
+        auto postdom_edges = CFA<BasicBlock>::CalculateDominators(
+            postdom_postorder,
+            function.AugmentedStructuralCFGSuccessorsFunction());
+        for (auto edge : postdom_edges) {
+          edge.first->SetImmediateStructuralPostDominator(edge.second);
+        }
+        /// calculate back edges.
+        CFA<BasicBlock>::DepthFirstTraversal(
+            function.pseudo_entry_block(),
+            function.AugmentedStructuralCFGSuccessorsFunction(), ignore_block,
+            ignore_block,
+            [&](const BasicBlock* from, const BasicBlock* to) {
+              // A back edge must be a real edge. Since the augmented successors
+              // contain structural edges, filter those from consideration.
+              for (const auto* succ : *(from->successors())) {
+                if (succ == to) back_edges.emplace_back(from->id(), to->id());
+              }
+            },
+            no_terminal_blocks);
+      }
+      UpdateContinueConstructExitBlocks(function, back_edges);
+
       if (auto error =
               StructuredControlFlowChecks(_, &function, back_edges, postorder))
         return error;
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index 25f8541..d15c79d 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -3541,9 +3541,11 @@
 OpBranch %16
 %16 = OpLabel
 OpSelectionMerge %18 None
-OpSwitch %13 %18 0 %17 1 %15
+OpSwitch %13 %18 0 %17 1 %19
 %17 = OpLabel
 OpStore %3 %uint_1
+OpBranch %19
+%19 = OpLabel
 OpBranch %15
 %15 = OpLabel
 OpBranch %12
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index b04c8f5..1095d3b 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -2570,9 +2570,8 @@
 
 TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop3) {
   // Checks that if a selection merge construct contains a conditional branch
-  // to the merge of a surrounding loop, the selection merge, and another block
-  // inside the selection merge, then we must keep the OpSelectionMerge
-  // instruction on that branch.
+  // to the selection merge, and another block inside the selection merge,
+  // then we must keep the OpSelectionMerge instruction on that branch.
   const std::string predefs = R"(
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -2586,6 +2585,7 @@
 %true = OpConstantTrue %bool
 %uint = OpTypeInt 32 0
 %undef_int = OpUndef %uint
+%undef_bool = OpUndef %bool
 )";
 
   const std::string body =
@@ -2596,7 +2596,7 @@
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
-; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge]]
 ; CHECK: [[sel_merge]] = OpLabel
@@ -2613,7 +2613,8 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
-OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
+;OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
+OpBranchConditional %undef_bool %sel_merge %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
 %bb4 = OpLabel
@@ -2632,9 +2633,8 @@
 
 TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue3) {
   // Checks that if a selection merge construct contains a conditional branch
-  // to the merge of a surrounding loop, the selection merge, and another block
-  // inside the selection merge, then we must keep the OpSelectionMerge
-  // instruction on that branch.
+  // the selection merge, and another block inside the selection merge, then we
+  // must keep the OpSelectionMerge instruction on that branch.
   const std::string predefs = R"(
 OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
@@ -2648,6 +2648,7 @@
 %true = OpConstantTrue %bool
 %uint = OpTypeInt 32 0
 %undef_int = OpUndef %uint
+%undef_bool = OpUndef %bool
 )";
 
   const std::string body =
@@ -2660,7 +2661,7 @@
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
 ; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
-; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_continue]] 1 [[bb3:%\w+]]
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge]]
 ; CHECK: [[sel_merge]] = OpLabel
@@ -2679,131 +2680,7 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
-OpSwitch %undef_int %sel_merge 0 %cont 1 %bb3
-%bb3 = OpLabel
-OpBranch %sel_merge
-%bb4 = OpLabel
-OpBranch %sel_merge
-%sel_merge = OpLabel
-OpBranch %loop_merge
-%cont = OpLabel
-OpBranch %loop_header
-%loop_merge = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
-}
-
-TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop4) {
-  // Same as |SelectionMergeWithExitToLoop|, except the branch in the selection
-  // construct is an |OpSwitch| instead of an |OpConditionalBranch|.  The
-  // OpSelectionMerge instruction is not needed in this case either.
-  const std::string predefs = R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-%void = OpTypeVoid
-%func_type = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%uint = OpTypeInt 32 0
-%undef_int = OpUndef %uint
-)";
-
-  const std::string body =
-      R"(
-; CHECK: OpLoopMerge [[loop_merge:%\w+]]
-; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-; CHECK: [[bb1]] = OpLabel
-; CHECK-NEXT: OpBranch [[bb2:%\w+]]
-; CHECK: [[bb2]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge
-; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
-; CHECK: [[bb3]] = OpLabel
-; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
-; CHECK: [[sel_merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_merge]]
-; CHECK: [[loop_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-%main = OpFunction %void None %func_type
-%entry_bb = OpLabel
-OpBranch %loop_header
-%loop_header = OpLabel
-OpLoopMerge %loop_merge %cont None
-OpBranch %bb1
-%bb1 = OpLabel
-OpSelectionMerge %sel_merge None
-OpBranchConditional %true %bb2 %bb4
-%bb2 = OpLabel
-OpSelectionMerge %bb3 None
-OpSwitch %undef_int %bb3 0 %loop_merge 1 %bb3
-%bb3 = OpLabel
-OpBranch %sel_merge
-%bb4 = OpLabel
-OpBranch %sel_merge
-%sel_merge = OpLabel
-OpBranch %loop_merge
-%cont = OpLabel
-OpBranch %loop_header
-%loop_merge = OpLabel
-OpReturn
-OpFunctionEnd
-)";
-
-  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
-}
-
-TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue4) {
-  // Same as |SelectionMergeWithExitToLoopContinue|, except the branch in the
-  // selection construct is an |OpSwitch| instead of an |OpConditionalBranch|.
-  // The OpSelectionMerge instruction is not needed in this case either.
-  const std::string predefs = R"(
-OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %main "main"
-OpExecutionMode %main OriginUpperLeft
-OpSource GLSL 140
-%void = OpTypeVoid
-%func_type = OpTypeFunction %void
-%bool = OpTypeBool
-%true = OpConstantTrue %bool
-%uint = OpTypeInt 32 0
-%undef_int = OpUndef %uint
-)";
-
-  const std::string body =
-      R"(
-; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]]
-; CHECK-NEXT: OpBranch [[bb1:%\w+]]
-; CHECK: [[bb1]] = OpLabel
-; CHECK-NEXT: OpBranch [[bb2:%\w+]]
-; CHECK: [[bb2]] = OpLabel
-; CHECK-NEXT: OpSelectionMerge
-; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_cont]] 1 [[bb3:%\w+]]
-; CHECK: [[bb3]] = OpLabel
-; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
-; CHECK: [[sel_merge]] = OpLabel
-; CHECK-NEXT: OpBranch [[loop_merge]]
-; CHECK: [[loop_merge]] = OpLabel
-; CHECK-NEXT: OpReturn
-%main = OpFunction %void None %func_type
-%entry_bb = OpLabel
-OpBranch %loop_header
-%loop_header = OpLabel
-OpLoopMerge %loop_merge %cont None
-OpBranch %bb1
-%bb1 = OpLabel
-OpSelectionMerge %sel_merge None
-OpBranchConditional %true %bb2 %bb4
-%bb2 = OpLabel
-OpSelectionMerge %bb3 None
-OpSwitch %undef_int %bb3 0 %cont 1 %bb3
+OpBranchConditional %undef_bool %sel_merge %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
 %bb4 = OpLabel
@@ -3036,9 +2913,11 @@
 ; CHECK-NEXT: OpLoopMerge [[outer_merge:%\w+]] [[outer_cont:%\w+]] None
 ; CHECK-NEXT: OpBranch [[inner:%\w+]]
 ; CHECK: [[inner]] = OpLabel
-; CHECK: OpLoopMerge [[outer_cont]] [[inner_cont:%\w+]] None
+; CHECK: OpLoopMerge [[inner_merge:%\w+]] [[inner_cont:%\w+]] None
 ; CHECK: [[inner_cont]] = OpLabel
 ; CHECK-NEXT: OpBranch [[inner]]
+; CHECK: [[inner_merge]] = OpLabel
+; CHECK-NEXT: OpUnreachable
 ; CHECK: [[outer_cont]] = OpLabel
 ; CHECK-NEXT: OpBranch [[outer]]
 ; CHECK: [[outer_merge]] = OpLabel
@@ -3058,7 +2937,7 @@
 OpLoopMerge %outer_merge %outer_continue None
 OpBranch %inner_loop
 %inner_loop = OpLabel
-OpLoopMerge %outer_continue %inner_continue None
+OpLoopMerge %inner_merge %inner_continue None
 OpBranch %inner_body
 %inner_body = OpLabel
 OpSelectionMerge %inner_continue None
@@ -3066,7 +2945,9 @@
 %ret = OpLabel
 OpReturn
 %inner_continue = OpLabel
-OpBranchConditional %true %outer_continue %inner_loop
+OpBranchConditional %true %inner_merge %inner_loop
+%inner_merge = OpLabel
+OpBranch %outer_continue
 %outer_continue = OpLabel
 OpBranchConditional %true %outer_merge %outer_loop
 %outer_merge = OpLabel
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index cefd8e5..d804f7c 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -1533,9 +1533,11 @@
 %9 = OpLabel
 OpBranch %10
 %10 = OpLabel
-OpLoopMerge %12 %10 None
+OpLoopMerge %12 %15 None
 OpBranch %14
 %14 = OpLabel
+OpBranch %15
+%15 = OpLabel
 OpBranchConditional %true %10 %12
 %12 = OpLabel
 OpReturn
@@ -1694,7 +1696,7 @@
 OpBranch %13
 %13 = OpLabel
 %14 = OpCopyObject %bool %false
-OpLoopMerge %16 %13 None
+OpLoopMerge %16 %22 None
 OpBranch %17
 %17 = OpLabel
 %19 = OpCopyObject %bool %true
@@ -1702,6 +1704,8 @@
 OpBranchConditional %true %20 %20
 %20 = OpLabel
 %21 = OpPhi %bool %19 %17
+OpBranch %22
+%22 = OpLabel
 OpBranchConditional %true %13 %16
 %16 = OpLabel
 OpReturn
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index 21960d1..04bd5d9 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -2231,7 +2231,7 @@
 
 TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
   // Make sure that the pass can handle a single block that is both a merge and
-  // a continue.
+  // a continue. Note that this is invalid SPIR-V.
   const std::string text =
       R"(
                OpCapability Shader
@@ -2265,7 +2265,7 @@
 )";
 
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true);
+  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, false);
 
   // Not looking for any particular output.  Other tests do that.
   // Just want to make sure the check for unreachable blocks does not emit an
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 7647746..afab3be 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -637,41 +637,6 @@
                    "  %Main = OpFunction %void None %9\n"));
 }
 
-TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) {
-  bool is_shader = GetParam() == SpvCapabilityShader;
-  Block entry("entry");
-  Block head("head", SpvOpBranchConditional);
-  Block f("f");
-  Block merge("merge", SpvOpReturn);
-
-  head.SetBody("%cond = OpSLessThan %boolt %one %two\n");
-
-  if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n");
-
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("head", "merge", std::make_pair("func", "Main")) +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
-
-  str += entry >> merge;
-  str += head >> std::vector<Block>({merge, f});
-  str += f >> merge;
-  str += merge;
-  str += "OpFunctionEnd\n";
-
-  CompileSuccessfully(str);
-  if (is_shader) {
-    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(
-        getDiagnosticString(),
-        MatchesRegex("The selection construct with the selection header "
-                     ".\\[%head\\] does not dominate the merge block "
-                     ".\\[%merge\\]\n  %merge = OpLabel\n"));
-  } else {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-  }
-}
-
 TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) {
   // If a merge block is reachable, then it must be strictly dominated by
   // its header block.
@@ -907,16 +872,7 @@
 
 TEST_P(ValidateCFG, UnreachableContinueUnreachableLoopInst) {
   CompileSuccessfully(GetUnreachableContinueUnreachableLoopInst(GetParam()));
-  if (GetParam() == SpvCapabilityShader) {
-    // Shader causes additional structured CFG checks that cause a failure.
-    ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-    EXPECT_THAT(getDiagnosticString(),
-                HasSubstr("Back-edges (1[%branch] -> 3[%target]) can only be "
-                          "formed between a block and a loop header."));
-
-  } else {
-    ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-  }
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
 std::string GetUnreachableMergeWithComplexBody(SpvCapability cap) {
@@ -1070,12 +1026,10 @@
   std::string header = GetDefaultHeader(cap);
 
   Block entry("entry");
-  Block foo("foo", SpvOpBranch);
   Block branch("branch", SpvOpBranch);
   Block merge("merge", SpvOpReturn);
   Block target("target", SpvOpBranch);
 
-  foo >> target;
   target >> branch;
 
   entry.AppendBody("%placeholder   = OpVariable %intptrt Function\n");
@@ -1092,7 +1046,6 @@
   str += branch >> std::vector<Block>({merge});
   str += merge;
   str += target;
-  str += foo;
   str += "OpFunctionEnd\n";
 
   return str;
@@ -1156,6 +1109,7 @@
   Block body("body", SpvOpBranchConditional);
   Block t("t", SpvOpReturn);
   Block f("f", SpvOpReturn);
+  Block pre_target("pre_target", SpvOpBranch);
 
   target >> branch;
   body.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
@@ -1163,10 +1117,10 @@
   std::string str = header;
   if (cap == SpvCapabilityShader) {
     branch.AppendBody("OpLoopMerge %merge %target None\n");
-    body.AppendBody("OpSelectionMerge %target None\n");
+    body.AppendBody("OpSelectionMerge %pre_target None\n");
   }
 
-  str += nameOps("branch", "merge", "target", "body", "t", "f",
+  str += nameOps("branch", "merge", "pre_target", "target", "body", "t", "f",
                  std::make_pair("func", "Main"));
   str += types_consts();
   str += "%func    = OpFunction %voidt None %funct\n";
@@ -1176,6 +1130,7 @@
   str += t;
   str += f;
   str += merge;
+  str += pre_target >> target;
   str += target;
   str += "OpFunctionEnd\n";
 
@@ -1296,9 +1251,10 @@
     loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n");
   }
 
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("loop2", "loop2_merge") + types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
+  std::string str =
+      GetDefaultHeader(GetParam()) +
+      nameOps("loop1", "loop1_cont_break_block", "loop2", "loop2_merge") +
+      types_consts() + "%func    = OpFunction %voidt None %funct\n";
 
   str += entry >> loop1;
   str += loop1 >> loop1_cont_break_block;
@@ -1824,40 +1780,6 @@
       << str << getDiagnosticString();
 }
 
-TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) {
-  // This test case ensures we don't count a Continue Target from a loop
-  // header to itself as a self-loop when computing back edges.
-  // Also, it detects that there is an edge from %latch to the pseudo-exit
-  // node, rather than from %loop.  In particular, it detects that we
-  // have used the *reverse* textual order of blocks when computing
-  // predecessor traversal roots.
-  bool is_shader = GetParam() == SpvCapabilityShader;
-  Block entry("entry");
-  Block loop("loop");
-  Block latch("latch");
-  Block merge("merge", SpvOpReturn);
-
-  entry.SetBody("%cond    = OpSLessThan %boolt %one %two\n");
-  if (is_shader) {
-    loop.SetBody("OpLoopMerge %merge %loop None\n");
-  }
-
-  std::string str = GetDefaultHeader(GetParam()) +
-                    nameOps("entry", "loop", "latch", "merge") +
-                    types_consts() +
-                    "%func    = OpFunction %voidt None %funct\n";
-
-  str += entry >> loop;
-  str += loop >> latch;
-  str += latch >> loop;
-  str += merge;
-  str += "OpFunctionEnd";
-
-  CompileSuccessfully(str);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions())
-      << str << getDiagnosticString();
-}
-
 // Unit test to check the case where a basic block is the entry block of 2
 // different constructs. In this case, the basic block is the entry block of a
 // continue construct as well as a selection construct. See issue# 517 for more
@@ -2872,8 +2794,8 @@
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 9 branches to the loop construct, but not "
-                        "to the loop header <ID> 7"));
+              HasSubstr("Back-edges (10[%10] -> 9[%9]) can only be formed "
+                        "between a block and a loop header"));
 }
 
 TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) {
@@ -3275,9 +3197,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("block <ID> 13[%13] exits the selection headed by <ID> "
-                        "9[%9], but not via a structured exit"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("The continue construct with the continue target 9[%9] is not "
+                "post dominated by the back-edge block 13[%13]"));
 }
 
 TEST_F(ValidateCFG, BreakFromSwitch) {