Use structured order to unroll loops. (#3443)

Fixes #3441

diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index 11f7e9c..ed0dd28 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -485,10 +485,27 @@
 
   if (include_pre_header && GetPreHeaderBlock())
     ordered_loop_blocks->push_back(loop_preheader_);
-  cfg.ForEachBlockInReversePostOrder(
-      loop_header_, [ordered_loop_blocks, this](BasicBlock* bb) {
-        if (IsInsideLoop(bb)) ordered_loop_blocks->push_back(bb);
-      });
+
+  bool is_shader =
+      context_->get_feature_mgr()->HasCapability(SpvCapabilityShader);
+  if (!is_shader) {
+    cfg.ForEachBlockInReversePostOrder(
+        loop_header_, [ordered_loop_blocks, this](BasicBlock* bb) {
+          if (IsInsideLoop(bb)) ordered_loop_blocks->push_back(bb);
+        });
+  } else {
+    // If this is a shader, it is possible that there are unreachable merge and
+    // continue blocks that must be copied to retain the structured order.
+    // The structured order will include these.
+    std::list<BasicBlock*> order;
+    cfg.ComputeStructuredOrder(loop_header_->GetParent(), loop_header_, &order);
+    for (BasicBlock* bb : order) {
+      if (bb == GetMergeBlock()) {
+        break;
+      }
+      ordered_loop_blocks->push_back(bb);
+    }
+  }
   if (include_merge && GetMergeBlock())
     ordered_loop_blocks->push_back(loop_merge_);
 }
diff --git a/test/opt/loop_optimizations/loop_fission.cpp b/test/opt/loop_optimizations/loop_fission.cpp
index e513f42..55b9c26 100644
--- a/test/opt/loop_optimizations/loop_fission.cpp
+++ b/test/opt/loop_optimizations/loop_fission.cpp
@@ -2203,11 +2203,11 @@
 %45 = OpAccessChain %18 %4 %39
 OpStore %45 %44
 %46 = OpIEqual %12 %39 %19
-OpSelectionMerge %47 None
-OpBranchConditional %46 %51 %47
+OpSelectionMerge %48 None
+OpBranchConditional %46 %47 %48
 %47 = OpLabel
 OpBranch %52
-%51 = OpLabel
+%48 = OpLabel
 OpBranch %52
 %52 = OpLabel
 %53 = OpIAdd %8 %39 %19
@@ -2242,16 +2242,16 @@
 OpReturn
 OpFunctionEnd
 )";
-  // clang-format on
-  std::unique_ptr<IRContext> context =
-      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
-                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
-  Module* module = context->module();
-  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
-                             << source << std::endl;
+// clang-format on
+std::unique_ptr<IRContext> context =
+    BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+                SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+Module* module = context->module();
+EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                           << source << std::endl;
 
-  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
-  SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
 }
 
 /*
diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp
index f551e7c..6030135 100644
--- a/test/opt/loop_optimizations/unroll_simple.cpp
+++ b/test/opt/loop_optimizations/unroll_simple.cpp
@@ -2998,6 +2998,76 @@
                                            kUnrollFactor);
 }
 
+// Test that a loop containing an unreachable merge block can still be unrolled
+// correctly.
+TEST_F(PassClassTest, UnreachableMerge) {
+  const std::string text = R"(
+; Identify the first iteration of the unrolled loop, and make sure it contains
+; the unreachable merge block.
+; The first SelectionMerge corresponds to the original loop merge.
+; The second is the branch in the loop.
+; CHECK: OpSelectionMerge {{%\w+}} None
+; CHECK: OpSelectionMerge [[unrch1:%\w+]] None
+; CHECK: [[unrch1]] = OpLabel
+; CHECK-NEXT: OpUnreachable
+; Identify the second iteration of the unrolled loop, and make sure it contains
+; the unreachable merge block.
+; The first SelectionMerge corresponds to the original loop merge
+; The second is the branch in the loop.
+; CHECK: OpSelectionMerge {{%\w+}} None
+; CHECK: OpSelectionMerge [[unrch2:%\w+]] None
+; CHECK: [[unrch2]] = OpLabel
+; CHECK-NEXT: OpUnreachable
+
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 64 1 1
+               OpSource HLSL 600
+               OpName %main "main"
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_2 = OpConstant %uint 2
+     %uint_1 = OpConstant %uint 1
+       %bool = OpTypeBool
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+       %main = OpFunction %void None %18
+         %23 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+         %28 = OpPhi %uint %uint_0 %23 %29 %27
+         %30 = OpULessThan %bool %28 %uint_2
+               OpLoopMerge %31 %27 Unroll
+               OpBranchConditional %30 %32 %31
+         %32 = OpLabel
+               OpSelectionMerge %33 None
+               OpSwitch %uint_0 %34
+         %34 = OpLabel
+         %35 = OpUndef %bool
+               OpSelectionMerge %36 None
+               OpBranchConditional %35 %37 %38
+         %38 = OpLabel
+               OpBranch %33
+         %37 = OpLabel
+               OpBranch %33
+         %36 = OpLabel
+               OpUnreachable
+         %33 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+         %29 = OpIAdd %uint %28 %uint_1
+               OpBranch %24
+         %31 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const bool kFullyUnroll = true;
+  const uint32_t kUnrollFactor = 0;
+  SinglePassRunAndMatch<opt::LoopUnroller>(text, true, kFullyUnroll,
+                                           kUnrollFactor);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools