// Copyright (c) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <string>

#include "gmock/gmock.h"
#include "spirv-tools/libspirv.hpp"
#include "spirv-tools/optimizer.hpp"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

using MergeReturnPassTest = PassTest<::testing::Test>;

TEST_F(MergeReturnPassTest, OneReturn) {
  const std::string before =
      R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %1 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%1 = OpFunction %2 None %3
%4 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after = before;

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

TEST_F(MergeReturnPassTest, TwoReturnsNoValue) {
  const std::string before =
      R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpReturn
%9 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpBranch %10
%9 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

TEST_F(MergeReturnPassTest, TwoReturnsWithValues) {
  const std::string before =
      R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
OpDecorate %7 LinkageAttributes "simple_kernel" Export
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
OpBranchConditional %3 %9 %10
%9 = OpLabel
OpReturnValue %4
%10 = OpLabel
OpReturnValue %5
OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
OpDecorate %7 LinkageAttributes "simple_kernel" Export
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
OpBranchConditional %3 %9 %10
%9 = OpLabel
OpBranch %11
%10 = OpLabel
OpBranch %11
%11 = OpLabel
%12 = OpPhi %1 %4 %9 %5 %10
OpReturnValue %12
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

TEST_F(MergeReturnPassTest, UnreachableReturnsNoValue) {
  const std::string before =
      R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpReturn
%8 = OpLabel
OpBranchConditional %4 %9 %10
%9 = OpLabel
OpReturn
%10 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Addresses
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpMemoryModel Physical32 OpenCL
OpEntryPoint Kernel %6 "simple_kernel"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranch %11
%8 = OpLabel
OpBranchConditional %4 %9 %10
%9 = OpLabel
OpBranch %11
%10 = OpLabel
OpBranch %11
%11 = OpLabel
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

TEST_F(MergeReturnPassTest, UnreachableReturnsWithValues) {
  const std::string before =
      R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
OpDecorate %7 LinkageAttributes "simple_kernel" Export
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
%9 = OpIAdd %1 %4 %5
OpReturnValue %9
%10 = OpLabel
OpBranchConditional %3 %11 %12
%11 = OpLabel
OpReturnValue %4
%12 = OpLabel
OpReturnValue %5
OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Linkage
OpCapability Kernel
OpMemoryModel Logical OpenCL
OpDecorate %7 LinkageAttributes "simple_kernel" Export
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%7 = OpFunction %1 None %6
%8 = OpLabel
%9 = OpIAdd %1 %4 %5
OpBranch %13
%10 = OpLabel
OpBranchConditional %3 %11 %12
%11 = OpLabel
OpBranch %13
%12 = OpLabel
OpBranch %13
%13 = OpLabel
%14 = OpPhi %1 %9 %8 %4 %11 %5 %12
OpReturnValue %14
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

TEST_F(MergeReturnPassTest, StructuredControlFlowWithUnreachableMerge) {
  const std::string before =
      R"(
; CHECK: [[false:%\w+]] = OpConstantFalse
; CHECK: [[true:%\w+]] = OpConstantTrue
; CHECK: OpFunction
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
; CHECK: OpSelectionMerge [[return_block:%\w+]]
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
; CHECK: [[if_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[then_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[merge_lab]] = OpLabel
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[return_block]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpReturn
%9 = OpLabel
OpReturn
%10 = OpLabel
OpUnreachable
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) {
  const std::string before =
      R"(
; CHECK: [[false:%\w+]] = OpConstantFalse
; CHECK: [[true:%\w+]] = OpConstantTrue
; CHECK: OpFunction
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
; CHECK: [[if_lab]] = OpLabel
; CHECK-NEXT: [[add:%\w+]] = OpIAdd [[type:%\w+]]
; CHECK-NEXT: OpBranch
; CHECK: [[then_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[dummy_loop_merge]]
; CHECK: [[merge_lab]] = OpLabel
; CHECK: [[dummy_loop_merge]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
%2 = OpTypeVoid
%3 = OpTypeBool
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %4 %8 %9
%8 = OpLabel
%11 = OpIAdd %int %int_0 %int_0
OpBranch %10
%9 = OpLabel
OpReturn
%10 = OpLabel
%12 = OpIAdd %int %11 %11
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, StructuredControlDecoration) {
  const std::string before =
      R"(
; CHECK: OpDecorate [[dec_id:%\w+]] RelaxedPrecision
; CHECK: [[false:%\w+]] = OpConstantFalse
; CHECK: [[true:%\w+]] = OpConstantTrue
; CHECK: OpFunction
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
; CHECK: OpSelectionMerge [[return_block:%\w+]]
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
; CHECK: [[if_lab]] = OpLabel
; CHECK-NEXT: [[dec_id]] = OpIAdd [[type:%\w+]]
; CHECK-NEXT: OpBranch
; CHECK: [[then_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[merge_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[return_block]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
OpDecorate %11 RelaxedPrecision
%2 = OpTypeVoid
%3 = OpTypeBool
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %4 %8 %9
%8 = OpLabel
%11 = OpIAdd %int %int_0 %int_0
OpBranch %10
%9 = OpLabel
OpReturn
%10 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, SplitBlockUsedInPhi) {
  const std::string before =
      R"(
; CHECK: OpFunction
; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[loop_merge:%\w+]]
; CHECK: [[loop_merge]] = OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]] [[old_code_path:%\w+]]
; CHECK: [[old_code_path:%\w+]] = OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[side_node:%\w+]] [[phi_block:%\w+]]
; CHECK: [[phi_block]] = OpLabel
; CHECK-NEXT: OpPhi %bool %false [[side_node]] %true [[old_code_path]]
               OpCapability Addresses
               OpCapability Shader
               OpCapability Linkage
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %1 "simple_shader"
       %void = OpTypeVoid
       %bool = OpTypeBool
      %false = OpConstantFalse %bool
       %true = OpConstantTrue %bool
          %6 = OpTypeFunction %void
          %1 = OpFunction %void None %6
          %7 = OpLabel
               OpLoopMerge %merge %cont None
               OpBranchConditional %false %9 %merge
          %9 = OpLabel
               OpReturn
       %cont = OpLabel
               OpBranch %7
      %merge = OpLabel
               OpSelectionMerge %merge2 None
               OpBranchConditional %false %if %merge2
         %if = OpLabel
               OpBranch %merge2
     %merge2 = OpLabel
         %12 = OpPhi %bool %false %if %true %merge
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

// TODO(#1861): Reenable these test when the breaks from selection constructs
// are reenabled.
/*
TEST_F(MergeReturnPassTest, UpdateOrderWhenPredicating) {
  const std::string before =
      R"(
; CHECK: OpFunction
; CHECK: OpFunction
; CHECK: OpSelectionMerge [[m1:%\w+]] None
; CHECK-NOT: OpReturn
; CHECK: [[m1]] = OpLabel
; CHECK: OpSelectionMerge [[m2:%\w+]] None
; CHECK: OpSelectionMerge [[m3:%\w+]] None
; CHECK: OpSelectionMerge [[m4:%\w+]] None
; CHECK: OpLabel
; CHECK-NEXT: OpStore
; CHECK-NEXT: OpBranch [[m4]]
; CHECK: [[m4]] = OpLabel
; CHECK-NEXT: [[ld4:%\w+]] = OpLoad %bool
; CHECK-NEXT: OpBranchConditional [[ld4]] [[m3]]
; CHECK: [[m3]] = OpLabel
; CHECK-NEXT: [[ld3:%\w+]] = OpLoad %bool
; CHECK-NEXT: OpBranchConditional [[ld3]] [[m2]]
; CHECK: [[m2]] = OpLabel
               OpCapability SampledBuffer
               OpCapability StorageImageExtendedFormats
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "PS_DebugTiles"
               OpExecutionMode %1 OriginUpperLeft
               OpSource HLSL 600
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
       %bool = OpTypeBool
          %1 = OpFunction %void None %3
          %5 = OpLabel
          %6 = OpFunctionCall %void %7
               OpReturn
               OpFunctionEnd
          %7 = OpFunction %void None %3
          %8 = OpLabel
          %9 = OpUndef %bool
               OpSelectionMerge %10 None
               OpBranchConditional %9 %11 %10
         %11 = OpLabel
               OpReturn
         %10 = OpLabel
         %12 = OpUndef %bool
               OpSelectionMerge %13 None
               OpBranchConditional %12 %14 %15
         %15 = OpLabel
         %16 = OpUndef %bool
               OpSelectionMerge %17 None
               OpBranchConditional %16 %18 %17
         %18 = OpLabel
               OpReturn
         %17 = OpLabel
               OpBranch %13
         %14 = OpLabel
               OpReturn
         %13 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}
*/

TEST_F(MergeReturnPassTest, StructuredControlFlowBothMergeAndHeader) {
  const std::string test =
      R"(
; CHECK: OpFunction
; CHECK: [[ret_flag:%\w+]] = OpVariable %_ptr_Function_bool Function %false
; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[loop1_merge:%\w+]] {{%\w+}}
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] {{%\w+}}
; CHECK: [[if_lab]] = OpLabel
; CHECK: OpStore [[ret_flag]] %true
; CHECK-NEXT: OpBranch [[loop1_merge]]
; CHECK: [[loop1_merge]] = OpLabel
; CHECK-NEXT: [[ld:%\w+]] = OpLoad %bool [[ret_flag]]
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional [[ld]] [[dummy_loop_merge]] [[empty_block:%\w+]]
; CHECK: [[empty_block]] = OpLabel
; CHECK-NEXT: OpBranch [[loop2:%\w+]]
; CHECK: [[loop2]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpLoopMerge
               OpCapability Addresses
               OpCapability Shader
               OpCapability Linkage
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %1 "simple_shader"
       %void = OpTypeVoid
       %bool = OpTypeBool
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
      %false = OpConstantFalse %bool
          %7 = OpTypeFunction %void
          %1 = OpFunction %void None %7
          %8 = OpLabel
               OpBranch %9
          %9 = OpLabel
               OpLoopMerge %10 %11 None
               OpBranchConditional %false %12 %13
         %12 = OpLabel
               OpReturn
         %13 = OpLabel
               OpBranch %10
         %11 = OpLabel
               OpBranch %9
         %10 = OpLabel
               OpLoopMerge %14 %15 None
               OpBranch %15
         %15 = OpLabel
         %16 = OpIAdd %uint %uint_0 %uint_0
               OpBranchConditional %false %10 %14
         %14 = OpLabel
         %17 = OpIAdd %uint %16 %16
               OpReturn
               OpFunctionEnd

)";

  const std::string after =
      R"(OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "simple_shader"
%void = OpTypeVoid
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%false = OpConstantFalse %bool
%7 = OpTypeFunction %void
%_ptr_Function_bool = OpTypePointer Function %bool
%true = OpConstantTrue %bool
%1 = OpFunction %void None %7
%8 = OpLabel
%18 = OpVariable %_ptr_Function_bool Function %false
OpSelectionMerge %9 None
OpBranchConditional %false %10 %11
%10 = OpLabel
OpStore %18 %true
OpBranch %9
%11 = OpLabel
OpBranch %9
%9 = OpLabel
%23 = OpLoad %bool %18
OpSelectionMerge %22 None
OpBranchConditional %23 %22 %21
%21 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %13
%13 = OpLabel
%14 = OpIAdd %uint %uint_0 %uint_0
OpBranchConditional %false %20 %12
%12 = OpLabel
%15 = OpIAdd %uint %14 %14
OpStore %18 %true
OpBranch %22
%22 = OpLabel
OpBranch %16
%16 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<MergeReturnPass>(test, false);
}

// TODO(#1861): Reenable these test when the breaks from selection constructs
// are reenabled.
/*
TEST_F(MergeReturnPassTest, NestedSelectionMerge) {
  const std::string before =
      R"(
               OpCapability Addresses
               OpCapability Shader
               OpCapability Linkage
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %1 "simple_shader"
       %void = OpTypeVoid
       %bool = OpTypeBool
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
      %false = OpConstantFalse %bool
          %7 = OpTypeFunction %void
          %1 = OpFunction %void None %7
          %8 = OpLabel
               OpSelectionMerge %9 None
               OpBranchConditional %false %10 %11
         %10 = OpLabel
               OpReturn
         %11 = OpLabel
               OpSelectionMerge %12 None
               OpBranchConditional %false %13 %14
         %13 = OpLabel
         %15 = OpIAdd %uint %uint_0 %uint_0
               OpBranch %12
         %14 = OpLabel
               OpReturn
         %12 = OpLabel
               OpBranch %9
          %9 = OpLabel
         %16 = OpIAdd %uint %15 %15
               OpReturn
               OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "simple_shader"
%void = OpTypeVoid
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%false = OpConstantFalse %bool
%7 = OpTypeFunction %void
%_ptr_Function_bool = OpTypePointer Function %bool
%true = OpConstantTrue %bool
%26 = OpUndef %uint
%1 = OpFunction %void None %7
%8 = OpLabel
%19 = OpVariable %_ptr_Function_bool Function %false
OpSelectionMerge %9 None
OpBranchConditional %false %10 %11
%10 = OpLabel
OpStore %19 %true
OpBranch %9
%11 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %false %13 %14
%13 = OpLabel
%15 = OpIAdd %uint %uint_0 %uint_0
OpBranch %12
%14 = OpLabel
OpStore %19 %true
OpBranch %12
%12 = OpLabel
%27 = OpPhi %uint %15 %13 %26 %14
%22 = OpLoad %bool %19
OpBranchConditional %22 %9 %21
%21 = OpLabel
OpBranch %9
%9 = OpLabel
%28 = OpPhi %uint %27 %21 %26 %10 %26 %12
%25 = OpLoad %bool %19
OpSelectionMerge %24 None
OpBranchConditional %25 %24 %23
%23 = OpLabel
%16 = OpIAdd %uint %28 %28
OpStore %19 %true
OpBranch %24
%24 = OpLabel
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

// This is essentially the same as NestedSelectionMerge, except
// the order of the first branch is changed.  This is to make sure things
// work even if the order of the traversals change.
TEST_F(MergeReturnPassTest, NestedSelectionMerge2) {
  const std::string before =
      R"(      OpCapability Addresses
               OpCapability Shader
               OpCapability Linkage
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %1 "simple_shader"
       %void = OpTypeVoid
       %bool = OpTypeBool
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
      %false = OpConstantFalse %bool
          %7 = OpTypeFunction %void
          %1 = OpFunction %void None %7
          %8 = OpLabel
               OpSelectionMerge %9 None
               OpBranchConditional %false %10 %11
         %11 = OpLabel
               OpReturn
         %10 = OpLabel
               OpSelectionMerge %12 None
               OpBranchConditional %false %13 %14
         %13 = OpLabel
         %15 = OpIAdd %uint %uint_0 %uint_0
               OpBranch %12
         %14 = OpLabel
               OpReturn
         %12 = OpLabel
               OpBranch %9
          %9 = OpLabel
         %16 = OpIAdd %uint %15 %15
               OpReturn
               OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "simple_shader"
%void = OpTypeVoid
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%false = OpConstantFalse %bool
%7 = OpTypeFunction %void
%_ptr_Function_bool = OpTypePointer Function %bool
%true = OpConstantTrue %bool
%26 = OpUndef %uint
%1 = OpFunction %void None %7
%8 = OpLabel
%19 = OpVariable %_ptr_Function_bool Function %false
OpSelectionMerge %9 None
OpBranchConditional %false %10 %11
%11 = OpLabel
OpStore %19 %true
OpBranch %9
%10 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %false %13 %14
%13 = OpLabel
%15 = OpIAdd %uint %uint_0 %uint_0
OpBranch %12
%14 = OpLabel
OpStore %19 %true
OpBranch %12
%12 = OpLabel
%27 = OpPhi %uint %15 %13 %26 %14
%25 = OpLoad %bool %19
OpBranchConditional %25 %9 %24
%24 = OpLabel
OpBranch %9
%9 = OpLabel
%28 = OpPhi %uint %27 %24 %26 %11 %26 %12
%23 = OpLoad %bool %19
OpSelectionMerge %22 None
OpBranchConditional %23 %22 %21
%21 = OpLabel
%16 = OpIAdd %uint %28 %28
OpStore %19 %true
OpBranch %22
%22 = OpLabel
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}

TEST_F(MergeReturnPassTest, NestedSelectionMerge3) {
  const std::string before =
      R"(      OpCapability Addresses
               OpCapability Shader
               OpCapability Linkage
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %1 "simple_shader"
       %void = OpTypeVoid
       %bool = OpTypeBool
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
      %false = OpConstantFalse %bool
          %7 = OpTypeFunction %void
          %1 = OpFunction %void None %7
          %8 = OpLabel
               OpSelectionMerge %9 None
               OpBranchConditional %false %10 %11
         %11 = OpLabel
               OpReturn
         %10 = OpLabel
         %12 = OpIAdd %uint %uint_0 %uint_0
               OpSelectionMerge %13 None
               OpBranchConditional %false %14 %15
         %14 = OpLabel
               OpBranch %13
         %15 = OpLabel
               OpReturn
         %13 = OpLabel
               OpBranch %9
          %9 = OpLabel
         %16 = OpIAdd %uint %12 %12
               OpReturn
               OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "simple_shader"
%void = OpTypeVoid
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%false = OpConstantFalse %bool
%7 = OpTypeFunction %void
%_ptr_Function_bool = OpTypePointer Function %bool
%true = OpConstantTrue %bool
%26 = OpUndef %uint
%1 = OpFunction %void None %7
%8 = OpLabel
%19 = OpVariable %_ptr_Function_bool Function %false
OpSelectionMerge %9 None
OpBranchConditional %false %10 %11
%11 = OpLabel
OpStore %19 %true
OpBranch %9
%10 = OpLabel
%12 = OpIAdd %uint %uint_0 %uint_0
OpSelectionMerge %13 None
OpBranchConditional %false %14 %15
%14 = OpLabel
OpBranch %13
%15 = OpLabel
OpStore %19 %true
OpBranch %13
%13 = OpLabel
%25 = OpLoad %bool %19
OpBranchConditional %25 %9 %24
%24 = OpLabel
OpBranch %9
%9 = OpLabel
%27 = OpPhi %uint %12 %24 %26 %11 %26 %13
%23 = OpLoad %bool %19
OpSelectionMerge %22 None
OpBranchConditional %23 %22 %21
%21 = OpLabel
%16 = OpIAdd %uint %27 %27
OpStore %19 %true
OpBranch %22
%22 = OpLabel
OpBranch %17
%17 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
}
*/

TEST_F(MergeReturnPassTest, NestedLoopMerge) {
  const std::string test =
      R"(
; CHECK: OpFunction
; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[outer_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[inner_loop_merge:%\w+]]
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpBranchConditional %true [[early_exit_block:%\w+]]
; CHECK: [[early_exit_block]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranch [[inner_loop_merge]]
; CHECK: [[inner_loop_merge]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[outer_loop_merge]]
; CHECK: [[outer_loop_merge]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]]
; CHECK: [[dummy_loop_merge]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpReturn
               OpCapability SampledBuffer
               OpCapability StorageImageExtendedFormats
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %2 "CS"
               OpExecutionMode %2 LocalSize 8 8 1
               OpSource HLSL 600
       %uint = OpTypeInt 32 0
       %void = OpTypeVoid
          %6 = OpTypeFunction %void
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
     %v3uint = OpTypeVector %uint 3
       %bool = OpTypeBool
       %true = OpConstantTrue %bool
%_ptr_Function_uint = OpTypePointer Function %uint
          %2 = OpFunction %void None %6
         %14 = OpLabel
               OpBranch %19
         %19 = OpLabel
         %20 = OpPhi %uint %uint_0 %2 %34 %23
         %21 = OpULessThan %bool %20 %uint_1
               OpLoopMerge %22 %23 DontUnroll
               OpBranchConditional %21 %24 %22
         %24 = OpLabel
               OpBranch %25
         %25 = OpLabel
         %27 = OpINotEqual %bool %uint_1 %uint_0
               OpLoopMerge %28 %29 DontUnroll
               OpBranchConditional %27 %30 %28
         %30 = OpLabel
               OpSelectionMerge %31 None
               OpBranchConditional %true %32 %31
         %32 = OpLabel
               OpReturn
         %31 = OpLabel
               OpBranch %29
         %29 = OpLabel
               OpBranch %25
         %28 = OpLabel
               OpBranch %23
         %23 = OpLabel
         %34 = OpIAdd %uint %20 %uint_1
               OpBranch %19
         %22 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<MergeReturnPass>(test, false);
}

TEST_F(MergeReturnPassTest, ReturnValueDecoration) {
  const std::string test =
      R"(
; CHECK: OpDecorate [[func:%\w+]] RelaxedPrecision
; CHECK: OpDecorate [[ret_val:%\w+]] RelaxedPrecision
; CHECK: [[func]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NOT: OpLabel
; CHECK: [[ret_val]] = OpVariable
OpCapability Linkage
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %11 "simple_shader"
OpDecorate %7 RelaxedPrecision
%12 = OpTypeVoid
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%13 = OpTypeFunction %12
%11 = OpFunction %12 None %13
%l1 = OpLabel
%fc = OpFunctionCall %1 %7
OpReturn
OpFunctionEnd
%7 = OpFunction %1 None %6
%8 = OpLabel
OpBranchConditional %3 %9 %10
%9 = OpLabel
OpReturnValue %4
%10 = OpLabel
OpReturnValue %5
OpFunctionEnd
)";

  SinglePassRunAndMatch<MergeReturnPass>(test, false);
}

TEST_F(MergeReturnPassTest,
       StructuredControlFlowWithNonTrivialUnreachableMerge) {
  const std::string before =
      R"(
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %4 %8 %9
%8 = OpLabel
OpReturn
%9 = OpLabel
OpReturn
%10 = OpLabel
%11 = OpUndef %3
OpUnreachable
OpFunctionEnd
)";

  std::vector<Message> messages = {
      {SPV_MSG_ERROR, nullptr, 0, 0,
       "Module contains unreachable blocks during merge return.  Run dead "
       "branch elimination before merge return."}};
  SetMessageConsumer(GetTestMessageConsumer(messages));

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  auto result = SinglePassRunToBinary<MergeReturnPass>(before, false);
  EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
  EXPECT_TRUE(messages.empty());
}

TEST_F(MergeReturnPassTest,
       StructuredControlFlowWithNonTrivialUnreachableContinue) {
  const std::string before =
      R"(
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranch %header
%header = OpLabel
OpLoopMerge %merge %continue None
OpBranchConditional %4 %8 %merge
%8 = OpLabel
OpReturn
%continue = OpLabel
%11 = OpUndef %3
OpBranch %header
%merge = OpLabel
OpReturn
OpFunctionEnd
)";

  std::vector<Message> messages = {
      {SPV_MSG_ERROR, nullptr, 0, 0,
       "Module contains unreachable blocks during merge return.  Run dead "
       "branch elimination before merge return."}};
  SetMessageConsumer(GetTestMessageConsumer(messages));

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  auto result = SinglePassRunToBinary<MergeReturnPass>(before, false);
  EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
  EXPECT_TRUE(messages.empty());
}

TEST_F(MergeReturnPassTest, StructuredControlFlowWithUnreachableBlock) {
  const std::string before =
      R"(
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %6 "simple_shader"
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantFalse %3
%1 = OpTypeFunction %2
%6 = OpFunction %2 None %1
%7 = OpLabel
OpBranch %header
%header = OpLabel
OpLoopMerge %merge %continue None
OpBranchConditional %4 %8 %merge
%8 = OpLabel
OpReturn
%continue = OpLabel
OpBranch %header
%merge = OpLabel
OpReturn
%unreachable = OpLabel
OpUnreachable
OpFunctionEnd
)";

  std::vector<Message> messages = {
      {SPV_MSG_ERROR, nullptr, 0, 0,
       "Module contains unreachable blocks during merge return.  Run dead "
       "branch elimination before merge return."}};
  SetMessageConsumer(GetTestMessageConsumer(messages));

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  auto result = SinglePassRunToBinary<MergeReturnPass>(before, false);
  EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
  EXPECT_TRUE(messages.empty());
}

TEST_F(MergeReturnPassTest, StructuredControlFlowDontChangeEntryPhi) {
  const std::string before =
      R"(
; CHECK: OpFunction %void
; CHECK: OpLabel
; CHECK: [[pre_header:%\w+]] = OpLabel
; CHECK: [[header:%\w+]] = OpLabel
; CHECK-NEXT: OpPhi %bool {{%\w+}} [[pre_header]] [[iv:%\w+]] [[continue:%\w+]]
; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]]
; CHECK: [[continue]] = OpLabel
; CHECK-NEXT: [[iv]] = Op
; CHECK: [[merge]] = OpLabel
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %1 "main"
       %void = OpTypeVoid
       %bool = OpTypeBool
          %4 = OpTypeFunction %void
          %1 = OpFunction %void None %4
          %5 = OpLabel
          %6 = OpUndef %bool
               OpBranch %7
          %7 = OpLabel
          %8 = OpPhi %bool %6 %5 %9 %10
               OpLoopMerge %11 %10 None
               OpBranch %12
         %12 = OpLabel
         %13 = OpUndef %bool
               OpSelectionMerge %10 DontFlatten
               OpBranchConditional %13 %10 %14
         %14 = OpLabel
               OpReturn
         %10 = OpLabel
          %9 = OpUndef %bool
               OpBranchConditional %13 %7 %11
         %11 = OpLabel
               OpReturn
               OpFunctionEnd

)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, StructuredControlFlowPartialReplacePhi) {
  const std::string before =
      R"(
; CHECK: OpFunction %void
; CHECK: OpLabel
; CHECK: [[pre_header:%\w+]] = OpLabel
; CHECK: [[header:%\w+]] = OpLabel
; CHECK-NEXT: OpPhi
; CHECK-NEXT: OpLoopMerge [[merge:%\w+]]
; CHECK: OpLabel
; CHECK: [[old_ret_block:%\w+]] = OpLabel
; CHECK: [[bb:%\w+]] = OpLabel
; CHECK-NEXT: [[val:%\w+]] = OpUndef %bool
; CHECK: [[merge]] = OpLabel
; CHECK-NEXT: [[phi1:%\w+]] = OpPhi %bool {{%\w+}} [[old_ret_block]] [[val]] [[bb]]
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb2:%\w+]]
; CHECK: [[bb2]] = OpLabel
; CHECK: OpBranch [[header2:%\w+]]
; CHECK: [[header2]] = OpLabel
; CHECK-NEXT: [[phi2:%\w+]] = OpPhi %bool [[phi1]] [[continue2:%\w+]] [[phi1]] [[bb2]]
; CHECK-NEXT: OpLoopMerge {{%\w+}} [[continue2]]
; CHECK: [[continue2]] = OpLabel
; CHECK-NEXT: OpBranch [[header2]]
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %1 "main"
       %void = OpTypeVoid
       %bool = OpTypeBool
          %4 = OpTypeFunction %void
          %1 = OpFunction %void None %4
          %5 = OpLabel
          %6 = OpUndef %bool
               OpBranch %7
          %7 = OpLabel
          %8 = OpPhi %bool %6 %5 %9 %10
               OpLoopMerge %11 %10 None
               OpBranch %12
         %12 = OpLabel
         %13 = OpUndef %bool
               OpSelectionMerge %10 DontFlatten
               OpBranchConditional %13 %10 %14
         %14 = OpLabel
               OpReturn
         %10 = OpLabel
          %9 = OpUndef %bool
               OpBranchConditional %13 %7 %11
         %11 = OpLabel
          %phi = OpPhi %bool %9 %10 %9 %cont
               OpLoopMerge %ret %cont None
               OpBranch %bb
         %bb = OpLabel
               OpBranchConditional %13 %ret %cont
         %cont = OpLabel
               OpBranch %11
         %ret = OpLabel
               OpReturn
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, GeneratePhiInOuterLoop) {
  const std::string before =
      R"(
      ; CHECK: OpSelectionMerge
      ; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
      ; CHECK-NEXT: [[def_bb1]] = OpLabel
      ; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]]
      ; CHECK: [[continue]] = OpLabel
      ; CHECK-NEXT: [[undef:%\w+]] = OpUndef
      ; CHECK: [[merge]] = OpLabel
      ; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool {{%\w+}} {{%\w+}} [[undef]] [[continue]]
      ; CHECK: OpCopyObject %bool [[phi]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
       %bool = OpTypeBool
          %8 = OpTypeFunction %bool
      %false = OpConstantFalse %bool
          %4 = OpFunction %void None %3
          %5 = OpLabel
         %63 = OpFunctionCall %bool %9
               OpReturn
               OpFunctionEnd
          %9 = OpFunction %bool None %8
         %10 = OpLabel
               OpBranch %31
         %31 = OpLabel
               OpLoopMerge %33 %34 None
               OpBranch %32
         %32 = OpLabel
               OpSelectionMerge %34 None
               OpBranchConditional %false %46 %34
         %46 = OpLabel
               OpLoopMerge %51 %52 None
               OpBranch %53
         %53 = OpLabel
               OpBranchConditional %false %50 %51
         %50 = OpLabel
               OpReturnValue %false
         %52 = OpLabel
               OpBranch %46
         %51 = OpLabel
               OpBranch %34
         %34 = OpLabel
         %64 = OpUndef %bool
               OpBranchConditional %false %31 %33
         %33 = OpLabel
               OpBranch %28
         %28 = OpLabel
         %60 = OpCopyObject %bool %64
               OpBranch %17
         %17 = OpLabel
               OpReturnValue %false
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, SerialLoopsUpdateBlockMapping) {
  // #2455: This test case triggers phi insertions that use previously inserted
  // phis. Without the fix, it fails to validate.
  const std::string spirv = R"(
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
; CHECK-NEXT: [[def_bb1]] = OpLabel
; CHECK: OpLoopMerge
; CHECK: OpLoopMerge
; CHECK: OpLoopMerge [[merge:%\w+]]
; CHECK: [[def:%\w+]] = OpFOrdLessThan
; CHECK: [[merge]] = OpLabel
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[def]]
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
; CHECK: [[cont]] = OpLabel
; CHECK-NEXT: OpBranchConditional [[phi]]
; CHECK-NOT: [[def]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main" %53
               OpExecutionMode %4 OriginUpperLeft
               OpSource ESSL 310
               OpDecorate %20 RelaxedPrecision
               OpDecorate %27 RelaxedPrecision
               OpDecorate %53 BuiltIn FragCoord
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeFloat 32
          %7 = OpTypeVector %6 3
          %8 = OpTypeFunction %7
         %11 = OpTypeBool
         %12 = OpConstantFalse %11
         %15 = OpConstant %6 1
         %16 = OpConstantComposite %7 %15 %15 %15
         %18 = OpTypeInt 32 1
         %19 = OpTypePointer Function %18
         %21 = OpConstant %18 1
         %28 = OpConstant %18 0
         %31 = OpTypePointer Function %11
         %33 = OpConstantTrue %11
         %51 = OpTypeVector %6 4
         %52 = OpTypePointer Input %51
         %53 = OpVariable %52 Input
         %54 = OpTypeInt 32 0
         %55 = OpConstant %54 0
         %56 = OpTypePointer Input %6
         %59 = OpConstant %6 0
         %76 = OpUndef %18
         %77 = OpUndef %11
         %78 = OpUndef %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %75 = OpFunctionCall %7 %9
               OpReturn
               OpFunctionEnd
          %9 = OpFunction %7 None %8
         %10 = OpLabel
         %20 = OpVariable %19 Function
               OpBranch %14
         %14 = OpLabel
               OpBranch %22
         %22 = OpLabel
         %27 = OpLoad %18 %20
               OpLoopMerge %24 %25 None
               OpBranch %24
         %25 = OpLabel
               OpBranch %22
         %24 = OpLabel
               OpBranch %34
         %34 = OpLabel
               OpLoopMerge %36 %40 None
               OpBranch %35
         %35 = OpLabel
               OpBranchConditional %77 %39 %40
         %39 = OpLabel
               OpReturnValue %16
         %40 = OpLabel
               OpBranchConditional %12 %34 %36
         %36 = OpLabel
               OpBranch %43
         %43 = OpLabel
               OpLoopMerge %45 %49 None
               OpBranch %44
         %44 = OpLabel
               OpBranchConditional %77 %48 %49
         %48 = OpLabel
               OpReturnValue %16
         %49 = OpLabel
         %60 = OpFOrdLessThan %11 %15 %59
               OpBranchConditional %12 %43 %45
         %45 = OpLabel
               OpBranch %62
         %62 = OpLabel
               OpLoopMerge %64 %68 None
               OpBranch %63
         %63 = OpLabel
               OpBranchConditional %77 %67 %68
         %67 = OpLabel
               OpReturnValue %16
         %68 = OpLabel
               OpBranchConditional %60 %62 %64
         %64 = OpLabel
               OpReturnValue %16
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
}

TEST_F(MergeReturnPassTest, InnerLoopMergeIsOuterLoopContinue) {
  const std::string before =
      R"(
      ; CHECK: OpSelectionMerge
      ; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
      ; CHECK-NEXT: [[def_bb1]] = OpLabel
      ; CHECK-NEXT: OpBranch [[outer_loop_header:%\w+]]
      ; CHECK: [[outer_loop_header]] = OpLabel
      ; CHECK-NEXT: OpLoopMerge [[outer_loop_merge:%\w+]] [[outer_loop_continue:%\w+]] None
      ; CHECK: [[outer_loop_continue]] = OpLabel
      ; CHECK-NEXT: OpBranch [[outer_loop_header]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %2 "main"
               OpExecutionMode %2 OriginUpperLeft
               OpSource ESSL 310
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
       %bool = OpTypeBool
          %6 = OpTypeFunction %bool
       %true = OpConstantTrue %bool
          %2 = OpFunction %void None %4
          %8 = OpLabel
          %9 = OpFunctionCall %bool %10
               OpReturn
               OpFunctionEnd
         %10 = OpFunction %bool None %6
         %11 = OpLabel
               OpBranch %12
         %12 = OpLabel
               OpLoopMerge %13 %14 None
               OpBranchConditional %true %15 %13
         %15 = OpLabel
               OpLoopMerge %14 %16 None
               OpBranchConditional %true %17 %14
         %17 = OpLabel
               OpReturnValue %true
         %16 = OpLabel
               OpBranch %15
         %14 = OpLabel
               OpBranch %12
         %13 = OpLabel
               OpReturnValue %true
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(before, false);
}

TEST_F(MergeReturnPassTest, BreakFromLoopUseNoLongerDominated) {
  const std::string spirv = R"(
; CHECK: [[undef:%\w+]] = OpUndef
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
; CHECK-NEXT: [[def_bb1]] = OpLabel
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
; CHECK-NEXT: OpBranch [[body:%\w+]]
; CHECK: [[body]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[non_ret:%\w+]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret:%\w+]] [[non_ret]]
; CHECK: [[ret]] = OpLabel
; CHECK-NEXT: OpStore
; CHECK-NEXT: OpBranch [[merge]]
; CHECK: [[non_ret]] = OpLabel
; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
; CHECK: [[break]] = OpLabel
; CHECK-NEXT: OpBranch [[merge]]
; CHECK: [[cont]] = OpLabel
; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
; CHECK: [[merge]] = OpLabel
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret]] [[def]] [[break]] [[def]] [[cont]]
; CHECK: OpLogicalNot {{%\w+}} [[phi]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func"
OpExecutionMode %func LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%func = OpFunction %void None %void_fn
%1 = OpLabel
OpBranch %2
%2 = OpLabel
OpLoopMerge %8 %7 None
OpBranch %3
%3 = OpLabel
OpSelectionMerge %5 None
OpBranchConditional %true %4 %5
%4 = OpLabel
OpReturn
%5 = OpLabel
%def = OpLogicalNot %bool %true
OpBranchConditional %true %6 %7
%6 = OpLabel
OpBranch %8
%7 = OpLabel
OpBranchConditional %true %2 %8
%8 = OpLabel
OpBranch %9
%9 = OpLabel
%use = OpLogicalNot %bool %def
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
}

TEST_F(MergeReturnPassTest, TwoBreaksFromLoopUsesNoLongerDominated) {
  const std::string spirv = R"(
; CHECK: [[undef:%\w+]] = OpUndef
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
; CHECK-NEXT: [[def_bb1]] = OpLabel
; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]]
; CHECK-NEXT: OpBranch [[body:%\w+]]
; CHECK: [[body]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[body2:%\w+]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret1:%\w+]] [[body2]]
; CHECK: [[ret1]] = OpLabel
; CHECK-NEXT: OpStore
; CHECK-NEXT: OpBranch [[merge]]
; CHECK: [[body2]] = OpLabel
; CHECK-NEXT: [[def1:%\w+]] = OpLogicalNot
; CHECK-NEXT: OpSelectionMerge [[body3:%\w+]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[ret2:%\w+]] [[body3:%\w+]]
; CHECK: [[ret2]] = OpLabel
; CHECK-NEXT: OpStore
; CHECK-NEXT: OpBranch [[merge]]
; CHECK: [[body3]] = OpLabel
; CHECK-NEXT: [[def2:%\w+]] = OpLogicalAnd
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[break:%\w+]] [[cont]]
; CHECK: [[break]] = OpLabel
; CHECK-NEXT: OpBranch [[merge]]
; CHECK: [[cont]] = OpLabel
; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
; CHECK: [[merge]] = OpLabel
; CHECK-NEXT: [[phi1:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def1]] [[break]] [[def1]] [[cont]]
; CHECK-NEXT: [[phi2:%\w+]] = OpPhi {{%\w+}} [[undef]] [[ret1]] [[undef]] [[ret2]] [[def2]] [[break]] [[def2]] [[cont]]
; CHECK: OpLogicalNot {{%\w+}} [[phi1]]
; CHECK: OpLogicalAnd {{%\w+}} [[phi2]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func"
OpExecutionMode %func LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%func = OpFunction %void None %void_fn
%1 = OpLabel
OpBranch %2
%2 = OpLabel
OpLoopMerge %10 %9 None
OpBranch %3
%3 = OpLabel
OpSelectionMerge %5 None
OpBranchConditional %true %4 %5
%4 = OpLabel
OpReturn
%5 = OpLabel
%def1 = OpLogicalNot %bool %true
OpSelectionMerge %7 None
OpBranchConditional %true %6 %7
%6 = OpLabel
OpReturn
%7 = OpLabel
%def2 = OpLogicalAnd %bool %true %true
OpBranchConditional %true %8 %9
%8 = OpLabel
OpBranch %10
%9 = OpLabel
OpBranchConditional %true %2 %10
%10 = OpLabel
OpBranch %11
%11 = OpLabel
%use1 = OpLogicalNot %bool %def1
%use2 = OpLogicalAnd %bool %def2 %true
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
}

TEST_F(MergeReturnPassTest, PredicateBreakBlock) {
  const std::string spirv = R"(
; IDs are being preserved so we can rely on basic block labels.
; CHECK: [[undef:%\w+]] = OpUndef
; CHECK: [[undef:%\w+]] = OpUndef
; CHECK: %13 = OpLabel
; CHECK-NEXT: [[def:%\w+]] = OpLogicalNot
; CHECK: %8 = OpLabel
; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} [[undef]] {{%\w+}} [[undef]] {{%\w+}} [[def]] %13 [[undef]] {{%\w+}}
; CHECK: OpLogicalAnd {{%\w+}} [[phi]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "func"
OpExecutionMode %1 LocalSize 1 1 1
%void = OpTypeVoid
%3 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpUndef %bool
%1 = OpFunction %void None %3
%6 = OpLabel
OpBranch %7
%7 = OpLabel
OpLoopMerge %8 %9 None
OpBranch %10
%10 = OpLabel
OpSelectionMerge %11 None
OpBranchConditional %true %12 %13
%12 = OpLabel
OpLoopMerge %14 %15 None
OpBranch %16
%16 = OpLabel
OpReturn
%15 = OpLabel
OpBranch %12
%14 = OpLabel
OpUnreachable
%13 = OpLabel
%17 = OpLogicalNot %bool %true
OpBranch %8
%11 = OpLabel
OpUnreachable
%9 = OpLabel
OpBranch %7
%8 = OpLabel
OpBranch %18
%18 = OpLabel
%19 = OpLogicalAnd %bool %17 %true
OpReturn
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(spirv, true);
}

TEST_F(MergeReturnPassTest, SingleReturnInLoop) {
  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource ESSL 310
%void = OpTypeVoid
%7 = OpTypeFunction %void
%float = OpTypeFloat 32
%9 = OpTypeFunction %float
%float_1 = OpConstant %float 1
)";

  const std::string caller =
      R"(
; CHECK: OpFunction
; CHECK: OpFunctionEnd
%main = OpFunction %void None %7
%22 = OpLabel
%30 = OpFunctionCall %float %f_
OpReturn
OpFunctionEnd
)";

  const std::string callee =
      R"(
; CHECK: OpFunction
; CHECK: OpLoopMerge [[merge:%\w+]]
; CHECK: [[merge]] = OpLabel
; CHECK: OpReturnValue
; CHECK-NEXT: OpFunctionEnd
%f_ = OpFunction %float None %9
%33 = OpLabel
OpBranch %34
%34 = OpLabel
OpLoopMerge %35 %36 None
OpBranch %37
%37 = OpLabel
OpReturnValue %float_1
%36 = OpLabel
OpBranch %34
%35 = OpLabel
OpUnreachable
OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(predefs + caller + callee, true);
}

TEST_F(MergeReturnPassTest, MergeToMergeBranch) {
  const std::string text =
      R"(
; CHECK: [[new_undef:%\w+]] = OpUndef %uint
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
; CHECK-NEXT: [[def_bb1]] = OpLabel
; CHECK: OpLoopMerge [[merge1:%\w+]]
; CHECK: OpLoopMerge [[merge2:%\w+]]
; CHECK: [[merge1]] = OpLabel
; CHECK-NEXT: OpPhi %uint [[new_undef]] [[merge2]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %2 "main"
               OpExecutionMode %2 LocalSize 100 1 1
               OpSource ESSL 310
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
       %uint = OpTypeInt 32 0
     %uint_1 = OpConstant %uint 1
       %bool = OpTypeBool
      %false = OpConstantFalse %bool
     %uint_0 = OpConstant %uint 0
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
      %int_1 = OpConstant %int 1
         %13 = OpUndef %bool
          %2 = OpFunction %void None %4
         %14 = OpLabel
               OpBranch %15
         %15 = OpLabel
               OpLoopMerge %16 %17 None
               OpBranch %18
         %18 = OpLabel
               OpLoopMerge %19 %20 None
               OpBranchConditional %13 %21 %19
         %21 = OpLabel
               OpReturn
         %20 = OpLabel
               OpBranch %18
         %19 = OpLabel
         %22 = OpUndef %uint
               OpBranch %23
         %23 = OpLabel
               OpBranch %16
         %17 = OpLabel
               OpBranch %15
         %16 = OpLabel
         %24 = OpCopyObject %uint %22
               OpReturn
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(text, true);
}

TEST_F(MergeReturnPassTest, PhiInSecondMerge) {
  //  Add and use a phi in the second merge block from the return.
  const std::string text =
      R"(
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
; CHECK-NEXT: [[def_bb1]] = OpLabel
; CHECK: OpLoopMerge [[merge_bb:%\w+]] [[continue_bb:%\w+]]
; CHECK: [[continue_bb]] = OpLabel
; CHECK-NEXT: [[val:%\w+]] = OpUndef %float
; CHECK: [[merge_bb]] = OpLabel
; CHECK-NEXT: [[phi:%\w+]] = OpPhi %float {{%\w+}} {{%\w+}} [[val]] [[continue_bb]]
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[old_merge:%\w+]]
; CHECK: [[old_merge]] = OpLabel
; CHECK-NEXT: OpConvertFToS %int [[phi]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %2 "main"
               OpExecutionMode %2 OriginUpperLeft
               OpSource ESSL 310
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
        %int = OpTypeInt 32 1
      %float = OpTypeFloat 32
       %bool = OpTypeBool
          %8 = OpUndef %bool
          %2 = OpFunction %void None %4
          %9 = OpLabel
               OpBranch %10
         %10 = OpLabel
               OpLoopMerge %11 %12 None
               OpBranch %13
         %13 = OpLabel
               OpLoopMerge %18 %14 None
               OpBranchConditional %8 %15 %18
         %15 = OpLabel
               OpReturn
         %14 = OpLabel
               OpBranch %13
         %18 = OpLabel
               OpBranch %12
         %12 = OpLabel
         %16 = OpUndef %float
               OpBranchConditional %8 %10 %11
         %11 = OpLabel
         %17 = OpConvertFToS %int %16
               OpReturn
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(text, true);
}

TEST_F(MergeReturnPassTest, ReturnsInSwitch) {
  //  Cannot branch directly to dummy switch merge block from original switch.
  //  Must branch to merge block of original switch and then do predicated
  //  branch to merge block of dummy switch.
  const std::string text =
      R"(
; CHECK: OpSelectionMerge [[dummy_merge_bb:%\w+]]
; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
; CHECK-NEXT: [[def_bb1]] = OpLabel
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpSwitch {{%\w+}} [[inner_merge_bb:%\w+]] 0 {{%\w+}} 1 {{%\w+}}
; CHECK: OpBranch [[inner_merge_bb]]
; CHECK: OpBranch [[inner_merge_bb]]
; CHECK-NEXT: [[inner_merge_bb]] = OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[dummy_merge_bb]] {{%\w+}}
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %PSMain "PSMain" %_entryPointOutput_color
               OpExecutionMode %PSMain OriginUpperLeft
               OpSource HLSL 500
               OpMemberDecorate %cb 0 Offset 0
               OpMemberDecorate %cb 1 Offset 16
               OpMemberDecorate %cb 2 Offset 32
               OpMemberDecorate %cb 3 Offset 48
               OpDecorate %cb Block
               OpDecorate %_ DescriptorSet 0
               OpDecorate %_ Binding 0
               OpDecorate %_entryPointOutput_color Location 0
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v4float = OpTypeVector %float 4
          %8 = OpTypeFunction %v4float
        %int = OpTypeInt 32 1
         %cb = OpTypeStruct %v4float %v4float %v4float %int
%_ptr_Uniform_cb = OpTypePointer Uniform %cb
          %_ = OpVariable %_ptr_Uniform_cb Uniform
      %int_3 = OpConstant %int 3
%_ptr_Uniform_int = OpTypePointer Uniform %int
      %int_0 = OpConstant %int 0
%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
      %int_1 = OpConstant %int 1
      %int_2 = OpConstant %int 2
    %float_0 = OpConstant %float 0
    %float_1 = OpConstant %float 1
         %45 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_color = OpVariable %_ptr_Output_v4float Output
     %PSMain = OpFunction %void None %3
          %5 = OpLabel
         %50 = OpFunctionCall %v4float %BlendValue_
               OpStore %_entryPointOutput_color %50
               OpReturn
               OpFunctionEnd
%BlendValue_ = OpFunction %v4float None %8
         %10 = OpLabel
         %21 = OpAccessChain %_ptr_Uniform_int %_ %int_3
         %22 = OpLoad %int %21
               OpSelectionMerge %25 None
               OpSwitch %22 %25 0 %23 1 %24
         %23 = OpLabel
         %28 = OpAccessChain %_ptr_Uniform_v4float %_ %int_0
         %29 = OpLoad %v4float %28
               OpReturnValue %29
         %24 = OpLabel
         %31 = OpAccessChain %_ptr_Uniform_v4float %_ %int_0
         %32 = OpLoad %v4float %31
         %34 = OpAccessChain %_ptr_Uniform_v4float %_ %int_1
         %35 = OpLoad %v4float %34
         %37 = OpAccessChain %_ptr_Uniform_v4float %_ %int_2
         %38 = OpLoad %v4float %37
         %39 = OpFMul %v4float %35 %38
         %40 = OpFAdd %v4float %32 %39
               OpReturnValue %40
         %25 = OpLabel
               OpReturnValue %45
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  SinglePassRunAndMatch<MergeReturnPass>(text, true);
}

TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) {
  // Make sure that the pass can handle a single block that is both a merge and
  // a continue.
  const std::string text =
      R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %2 "main"
               OpExecutionMode %2 OriginUpperLeft
               OpSource ESSL 310
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
       %bool = OpTypeBool
       %true = OpConstantTrue %bool
          %2 = OpFunction %void None %4
          %7 = OpLabel
               OpBranch %8
          %8 = OpLabel
               OpLoopMerge %9 %10 None
               OpBranch %11
         %11 = OpLabel
               OpSelectionMerge %10 None
               OpBranchConditional %true %12 %13
         %12 = OpLabel
               OpReturn
         %13 = OpLabel
               OpReturn
         %10 = OpLabel
               OpBranch %8
          %9 = OpLabel
               OpUnreachable
               OpFunctionEnd
)";

  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  auto result = SinglePassRunAndDisassemble<MergeReturnPass>(text, true, true);

  // Not looking for any particular output.  Other tests do that.
  // Just want to make sure the check for unreachable blocks does not emit an
  // error.
  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
}

}  // namespace
}  // namespace opt
}  // namespace spvtools
