ADCE: Only mark true breaks and continues of live loops
authorGregF <greg@LunarG.com>
Tue, 12 Dec 2017 21:27:46 +0000 (14:27 -0700)
committerDavid Neto <dneto@google.com>
Fri, 15 Dec 2017 16:53:57 +0000 (11:53 -0500)
This fixes issue #1075

- Mark continue when conditional branch with merge block.
  Only mark if merge block is not continue block.

- Handle conditional branch break with preceding merge

source/opt/aggressive_dead_code_elim_pass.cpp
source/opt/aggressive_dead_code_elim_pass.h
test/opt/aggressive_dead_code_elim_test.cpp

index b9b5486..37dca13 100644 (file)
@@ -120,39 +120,39 @@ bool AggressiveDCEPass::IsStructuredIfOrLoopHeader(ir::BasicBlock* bp,
 
 void AggressiveDCEPass::ComputeBlock2HeaderMaps(
     std::list<ir::BasicBlock*>& structuredOrder) {
-  block2headerMerge_.clear();
   block2headerBranch_.clear();
-  std::stack<ir::Instruction*> currentMergeInst;
-  std::stack<ir::Instruction*> currentBranchInst;
-  std::stack<uint32_t> currentMergeBlockId;
-  currentMergeInst.push(nullptr);
-  currentBranchInst.push(nullptr);
-  currentMergeBlockId.push(0);
+  branch2merge_.clear();
+  std::stack<ir::Instruction*> currentHeaderBranch;
+  currentHeaderBranch.push(nullptr);
+  uint32_t currentMergeBlockId = 0;
   for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
-    // If leaving an if or loop, update stacks
-    if ((*bi)->id() == currentMergeBlockId.top()) {
-      currentMergeBlockId.pop();
-      currentMergeInst.pop();
-      currentBranchInst.pop();
+    // If this block is the merge block of the current control construct,
+    // we are leaving the current construct so we must update state
+    if ((*bi)->id() == currentMergeBlockId) {
+      currentHeaderBranch.pop();
+      ir::Instruction* chb = currentHeaderBranch.top();
+      if (chb != nullptr)
+        currentMergeBlockId = branch2merge_[chb]->GetSingleWordInOperand(0);
     }
     ir::Instruction* mergeInst;
     ir::Instruction* branchInst;
     uint32_t mergeBlockId;
     bool is_header =
         IsStructuredIfOrLoopHeader(*bi, &mergeInst, &branchInst, &mergeBlockId);
-    // If there is live code in loop header, the loop is live
+    // If this is a loop header, update state first so the block will map to
+    // the loop.
     if (is_header && mergeInst->opcode() == SpvOpLoopMerge) {
-      currentMergeBlockId.push(mergeBlockId);
-      currentMergeInst.push(mergeInst);
-      currentBranchInst.push(branchInst);
+      currentHeaderBranch.push(branchInst);
+      branch2merge_[branchInst] = mergeInst;
+      currentMergeBlockId = mergeBlockId;
     }
-    block2headerMerge_[*bi] = currentMergeInst.top();
-    block2headerBranch_[*bi] = currentBranchInst.top();
-    // If there is live code following if header, the if is live
+    // Map the block to the current construct.
+    block2headerBranch_[*bi] = currentHeaderBranch.top();
+    // If this is an if header, update state so following blocks map to the if.
     if (is_header && mergeInst->opcode() == SpvOpSelectionMerge) {
-      currentMergeBlockId.push(mergeBlockId);
-      currentMergeInst.push(mergeInst);
-      currentBranchInst.push(branchInst);
+      currentHeaderBranch.push(branchInst);
+      branch2merge_[branchInst] = mergeInst;
+      currentMergeBlockId = mergeBlockId;
     }
   }
 }
@@ -172,11 +172,63 @@ void AggressiveDCEPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) {
   bp->AddInstruction(std::move(newBranch));
 }
 
-void AggressiveDCEPass::AddBranchesToWorklist(uint32_t labelId) {
-  get_def_use_mgr()->ForEachUser(labelId, [this](ir::Instruction* user) {
+void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
+    ir::Instruction* loopMerge) {
+  const uint32_t mergeId =
+      loopMerge->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx);
+  get_def_use_mgr()->ForEachUser(
+      mergeId, [&loopMerge, this](ir::Instruction* user) {
+        // A branch to the merge block can only be a break if it is nested in
+        // the current loop
+        SpvOp op = user->opcode();
+        if (op != SpvOpBranchConditional && op != SpvOpBranch) return;
+        ir::Instruction* branchInst = user;
+        while (true) {
+          ir::BasicBlock* blk = inst2block_[branchInst];
+          ir::Instruction* hdrBranch = block2headerBranch_[blk];
+          if (hdrBranch == nullptr) return;
+          ir::Instruction* hdrMerge = branch2merge_[hdrBranch];
+          if (hdrMerge == loopMerge) break;
+          branchInst = hdrBranch;
+        }
+        if (!IsLive(user)) {
+          AddToWorklist(user);
+          // Add branch's merge if there is one
+          ir::Instruction* userMerge = branch2merge_[user];
+          if (userMerge != nullptr) AddToWorklist(userMerge);
+        }
+      });
+  const uint32_t contId =
+      loopMerge->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx);
+  get_def_use_mgr()->ForEachUser(contId, [&contId,
+                                          this](ir::Instruction* user) {
     SpvOp op = user->opcode();
-    if (op == SpvOpBranchConditional || op == SpvOpBranch)
-      if (!IsLive(user)) AddToWorklist(user);
+    if (op == SpvOpBranchConditional) {
+      // A conditional branch can only be a continue if it does not have a merge
+      // instruction or its merge block is not the continue block.
+      ir::Instruction* hdrMerge = branch2merge_[user];
+      if (hdrMerge != nullptr && hdrMerge->opcode() == SpvOpSelectionMerge) {
+        uint32_t hdrMergeId =
+            hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
+        if (hdrMergeId == contId) return;
+        // Need to mark merge instruction too
+        if (!IsLive(hdrMerge)) AddToWorklist(hdrMerge);
+      }
+    } else if (op == SpvOpBranch) {
+      // An unconditional branch can only be a continue if it is not
+      // branching to its own merge block.
+      ir::BasicBlock* blk = inst2block_[user];
+      ir::Instruction* hdrBranch = block2headerBranch_[blk];
+      if (hdrBranch == nullptr) return;
+      ir::Instruction* hdrMerge = branch2merge_[hdrBranch];
+      if (hdrMerge->opcode() == SpvOpLoopMerge) return;
+      uint32_t hdrMergeId =
+          hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
+      if (contId == hdrMergeId) return;
+    } else {
+      return;
+    }
+    if (!IsLive(user)) AddToWorklist(user);
   });
 }
 
@@ -196,9 +248,8 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
   func_is_entry_point_ = false;
   private_stores_.clear();
   // Stacks to keep track of when we are inside an if- or loop-construct.
-  // When not immediately inside an if- or loop-construct,  we must assume
-  // all branches are live as we may be inside of a control construct (ie
-  // switch) which is not part of the ADCE analysis.
+  // When immediately inside an if- or loop-construct, we do not initially
+  // mark branches live. All other branches must be marked live.
   std::stack<bool> assume_branches_live;
   std::stack<uint32_t> currentMergeBlockId;
   // Push sentinel values on stack for when outside of any control flow.
@@ -294,16 +345,11 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
     ir::Instruction* branchInst = block2headerBranch_[blk];
     if (branchInst != nullptr && !IsLive(branchInst)) {
       AddToWorklist(branchInst);
-      ir::Instruction* mergeInst = block2headerMerge_[blk];
+      ir::Instruction* mergeInst = branch2merge_[branchInst];
       AddToWorklist(mergeInst);
-      // If in a loop, mark all branches targeting merge block
-      // and continue block as live.
-      if (mergeInst->opcode() == SpvOpLoopMerge) {
-        AddBranchesToWorklist(
-            mergeInst->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx));
-        AddBranchesToWorklist(
-            mergeInst->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx));
-      }
+      // If in a loop, mark all its break and continue instructions live
+      if (mergeInst->opcode() == SpvOpLoopMerge)
+        AddBreaksAndContinuesToWorklist(mergeInst);
     }
     // If local load, add all variable's stores if variable not already live
     if (liveInst->opcode() == SpvOpLoad) {
index 1912e0f..0793000 100644 (file)
@@ -93,8 +93,8 @@ class AggressiveDCEPass : public MemPass {
                                   ir::Instruction** branchInst,
                                   uint32_t* mergeBlockId);
 
-  // Initialize block2branch_ and block2merge_ using |structuredOrder| to
-  // order blocks.
+  // Initialize block2headerBranch_ and branch2merge_ using |structuredOrder|
+  // to order blocks.
   void ComputeBlock2HeaderMaps(std::list<ir::BasicBlock*>& structuredOrder);
 
   // Initialize inst2block_ for |func|.
@@ -103,8 +103,9 @@ class AggressiveDCEPass : public MemPass {
   // Add branch to |labelId| to end of block |bp|.
   void AddBranch(uint32_t labelId, ir::BasicBlock* bp);
 
-  // Add all branches targeting |labelId| to worklist if not already live
-  void AddBranchesToWorklist(uint32_t labelId);
+  // Add all break and continue branches in the loop associated with
+  // |mergeInst| to worklist if not already live
+  void AddBreaksAndContinuesToWorklist(ir::Instruction* mergeInst);
 
   // For function |func|, mark all Stores to non-function-scope variables
   // and block terminating instructions as live. Recursively mark the values
@@ -137,12 +138,13 @@ class AggressiveDCEPass : public MemPass {
   std::queue<ir::Instruction*> worklist_;
 
   // Map from block to the branch instruction in the header of the most
-  // immediate controlling structured if.
+  // immediate controlling structured if or loop.  A loop header block points
+  // to its own branch instruction.  An if-selection block points to the branch
+  // of an enclosing construct's header, if one exists.
   std::unordered_map<ir::BasicBlock*, ir::Instruction*> block2headerBranch_;
 
-  // Map from block to the merge instruction in the header of the most
-  // immediate controlling structured if.
-  std::unordered_map<ir::BasicBlock*, ir::Instruction*> block2headerMerge_;
+  // Map from branch to its associated merge instruction, if any
+  std::unordered_map<ir::Instruction*, ir::Instruction*> branch2merge_;
 
   // Map from instruction containing block
   std::unordered_map<ir::Instruction*, ir::BasicBlock*> inst2block_;
index 81c157a..add81d9 100644 (file)
@@ -2035,6 +2035,105 @@ OpFunctionEnd
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(assembly, assembly, true, true);
 }
 
+TEST_F(AggressiveDCETest, NoEliminateIfBreak2) {
+  // Do not eliminate break as conditional branch with merge instruction
+  // Note: SPIR-V edited to add merge instruction before break.
+  //
+  // #version 430
+  //
+  // layout(std430) buffer U_t
+  // {
+  //     float g_F[10];
+  // };
+  //
+  // layout(location = 0)out float o;
+  //
+  // void main(void)
+  // {
+  //     float s = 0.0;
+  //     for (int i=0; i<10; i++)
+  //         s += g_F[i];
+  //     o = s;
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = 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
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpSelectionMerge %33 None
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %34
+%36 = OpLoad %float %35
+%37 = OpLoad %float %s
+%38 = OpFAdd %float %37 %36
+OpStore %s %38
+OpBranch %29
+%29 = OpLabel
+%39 = OpLoad %int %i
+%40 = OpIAdd %int %39 %int_1
+OpStore %i %40
+OpBranch %27
+%28 = OpLabel
+%41 = OpLoad %float %s
+OpStore %o %41
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
 TEST_F(AggressiveDCETest, EliminateEntireUselessLoop) {
   // #version 140
   // in vec4 BaseColor;
@@ -2831,6 +2930,130 @@ OpFunctionEnd
       predefs_before + func_before, predefs_after + func_after, true, true);
 }
 
+TEST_F(AggressiveDCETest, EliminateEmptyIfBeforeContinue) {
+  // #version 430
+  //
+  // layout(location = 0)out float o;
+  //
+  // void main(void)
+  // {
+  //     float s = 0.0;
+  //     for (int i=0; i<10; i++) {
+  //         s += 1.0;
+  //         if (i > s) {}
+  //     }
+  //     o = s;
+  // }
+
+  const std::string predefs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %3
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpDecorate %3 Location 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%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
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%3 = OpVariable %_ptr_Output_float Output
+)";
+
+  const std::string predefs_after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %3
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpDecorate %3 Location 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%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
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%3 = OpVariable %_ptr_Output_float Output
+)";
+
+  const std::string func_before =
+      R"(%main = OpFunction %void None %5
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpPhi %float %float_0 %16 %19 %20
+%21 = OpPhi %int %int_0 %16 %22 %20
+OpLoopMerge %23 %20 None
+OpBranch %24
+%24 = OpLabel
+%25 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %25 %26 %23
+%26 = OpLabel
+%19 = OpFAdd %float %18 %float_1
+%27 = OpConvertFToS %int %19
+%28 = OpSGreaterThan %bool %21 %27
+OpSelectionMerge %20 None
+OpBranchConditional %28 %29 %20
+%29 = OpLabel
+OpBranch %20
+%20 = OpLabel
+%22 = OpIAdd %int %21 %int_1
+OpBranch %17
+%23 = OpLabel
+OpStore %3 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string func_after =
+      R"(%main = OpFunction %void None %5
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpPhi %float %float_0 %16 %19 %20
+%21 = OpPhi %int %int_0 %16 %22 %20
+OpLoopMerge %23 %20 None
+OpBranch %24
+%24 = OpLabel
+%25 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %25 %26 %23
+%26 = OpLabel
+%19 = OpFAdd %float %18 %float_1
+OpBranch %20
+%20 = OpLabel
+%22 = OpIAdd %int %21 %int_1
+OpBranch %17
+%23 = OpLabel
+OpStore %3 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
 TEST_F(AggressiveDCETest, NoEliminateLiveNestedLoopWithIf) {
   // Note: SPIR-V optimized
   //
@@ -2960,6 +3183,323 @@ OpFunctionEnd
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(assembly, assembly, true, true);
 }
 
+TEST_F(AggressiveDCETest, NoEliminateIfContinue) {
+  // Do not eliminate continue embedded in if construct
+  //
+  // #version 430
+  //
+  // layout(std430) buffer U_t
+  // {
+  //     float g_F[10];
+  // };
+  //
+  // layout(location = 0)out float o;
+  //
+  // void main(void)
+  // {
+  //     float s = 0.0;
+  //     for (int i=0; i<10; i++) {
+  //         if (i % 2 == 0) continue;
+  //         s += g_F[i];
+  //     }
+  //     o = s;
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = 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
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSMod %int %34 %int_2
+%36 = OpIEqual %bool %35 %int_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+OpBranch %29
+%37 = OpLabel
+%39 = OpLoad %int %i
+%40 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %39
+%41 = OpLoad %float %40
+%42 = OpLoad %float %s
+%43 = OpFAdd %float %42 %41
+OpStore %s %43
+OpBranch %29
+%29 = OpLabel
+%44 = OpLoad %int %i
+%45 = OpIAdd %int %44 %int_1
+OpStore %i %45
+OpBranch %27
+%28 = OpLabel
+%46 = OpLoad %float %s
+OpStore %o %46
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfContinue2) {
+  // Do not eliminate continue not embedded in if construct
+  //
+  // #version 430
+  //
+  // layout(std430) buffer U_t
+  // {
+  //     float g_F[10];
+  // };
+  //
+  // layout(location = 0)out float o;
+  //
+  // void main(void)
+  // {
+  //     float s = 0.0;
+  //     for (int i=0; i<10; i++) {
+  //         if (i % 2 == 0) continue;
+  //         s += g_F[i];
+  //     }
+  //     o = s;
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = 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
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSMod %int %34 %int_2
+%36 = OpIEqual %bool %35 %int_0
+OpBranchConditional %36 %29 %37
+%37 = OpLabel
+%38 = OpLoad %int %i
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %38
+%40 = OpLoad %float %39
+%41 = OpLoad %float %s
+%42 = OpFAdd %float %41 %40
+OpStore %s %42
+OpBranch %29
+%29 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpIAdd %int %43 %int_1
+OpStore %i %44
+OpBranch %27
+%28 = OpLabel
+%45 = OpLoad %float %s
+OpStore %o %45
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfContinue3) {
+  // Do not eliminate continue as conditional branch with merge instruction
+  // Note: SPIR-V edited to add merge instruction before continue.
+  //
+  // #version 430
+  //
+  // layout(std430) buffer U_t
+  // {
+  //     float g_F[10];
+  // };
+  //
+  // layout(location = 0)out float o;
+  //
+  // void main(void)
+  // {
+  //     float s = 0.0;
+  //     for (int i=0; i<10; i++) {
+  //         if (i % 2 == 0) continue;
+  //         s += g_F[i];
+  //     }
+  //     o = s;
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = 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
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSMod %int %34 %int_2
+%36 = OpIEqual %bool %35 %int_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %29 %37
+%37 = OpLabel
+%38 = OpLoad %int %i
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %38
+%40 = OpLoad %float %39
+%41 = OpLoad %float %s
+%42 = OpFAdd %float %41 %40
+OpStore %s %42
+OpBranch %29
+%29 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpIAdd %int %43 %int_1
+OpStore %i %44
+OpBranch %27
+%28 = OpLabel
+%45 = OpLoad %float %s
+OpStore %o %45
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required