// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG 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 "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

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

TEST_F(DeadBranchElimTest, IfThenElseTrue) {
  // #version 140
  //
  // in vec4 BaseColor;
  //
  // void main()
  // {
  //     vec4 v;
  //     if (true)
  //       v = vec4(0.0,0.0,0.0,0.0);
  //     else
  //       v = vec4(1.0,1.0,1.0,1.0);
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %gl_FragColor "gl_FragColor"
OpName %BaseColor "BaseColor"
%void = OpTypeVoid
%7 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%16 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%19 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpSelectionMerge %20 None
OpBranchConditional %true %21 %22
%21 = OpLabel
OpStore %v %14
OpBranch %20
%22 = OpLabel
OpStore %v %16
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%19 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpBranch %21
%21 = OpLabel
OpStore %v %14
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, IfThenElseFalse) {
  // #version 140
  //
  // in vec4 BaseColor;
  //
  // void main()
  // {
  //     vec4 v;
  //     if (false)
  //       v = vec4(0.0,0.0,0.0,0.0);
  //     else
  //       v = vec4(1.0,1.0,1.0,1.0);
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %gl_FragColor "gl_FragColor"
OpName %BaseColor "BaseColor"
%void = OpTypeVoid
%7 = OpTypeFunction %void
%bool = OpTypeBool
%false = OpConstantFalse %bool
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%16 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%19 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpSelectionMerge %20 None
OpBranchConditional %false %21 %22
%21 = OpLabel
OpStore %v %14
OpBranch %20
%22 = OpLabel
OpStore %v %16
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%19 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpBranch %22
%22 = OpLabel
OpStore %v %16
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, IfThenElseNull) {
  // For booleans OpConstantNull should be treated similar to OpConstantFalse.
  //
  // From the SPIR-V spec:
  // OpConstantNull: Declares a new null constant value.
  // The null value is type dependent, defined as follows:
  // - Scalar Boolean: false
  // ...

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %gl_FragColor "gl_FragColor"
OpName %BaseColor "BaseColor"
%void = OpTypeVoid
%7 = OpTypeFunction %void
%bool = OpTypeBool
%9 = OpConstantNull %bool
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%16 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%19 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpSelectionMerge %20 None
OpBranchConditional %9 %21 %22
%21 = OpLabel
OpStore %v %14
OpBranch %20
%22 = OpLabel
OpStore %v %16
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%19 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpBranch %22
%22 = OpLabel
OpStore %v %16
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, IfThenTrue) {
  // #version 140
  //
  // in vec4 BaseColor;
  //
  // void main()
  // {
  //     vec4 v = BaseColor;
  //     if (true)
  //       v = v * vec4(0.5,0.5,0.5,0.5);
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %BaseColor "BaseColor"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%7 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%true = OpConstantTrue %bool
%float_0_5 = OpConstant %float 0.5
%15 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%17 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%18 = OpLoad %v4float %BaseColor
OpStore %v %18
OpSelectionMerge %19 None
OpBranchConditional %true %20 %19
%20 = OpLabel
%21 = OpLoad %v4float %v
%22 = OpFMul %v4float %21 %15
OpStore %v %22
OpBranch %19
%19 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%17 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%18 = OpLoad %v4float %BaseColor
OpStore %v %18
OpBranch %20
%20 = OpLabel
%21 = OpLoad %v4float %v
%22 = OpFMul %v4float %21 %15
OpStore %v %22
OpBranch %19
%19 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, IfThenFalse) {
  // #version 140
  //
  // in vec4 BaseColor;
  //
  // void main()
  // {
  //     vec4 v = BaseColor;
  //     if (false)
  //       v = v * vec4(0.5,0.5,0.5,0.5);
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %BaseColor "BaseColor"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%7 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%false = OpConstantFalse %bool
%float_0_5 = OpConstant %float 0.5
%15 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%17 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%18 = OpLoad %v4float %BaseColor
OpStore %v %18
OpSelectionMerge %19 None
OpBranchConditional %false %20 %19
%20 = OpLabel
%21 = OpLoad %v4float %v
%22 = OpFMul %v4float %21 %15
OpStore %v %22
OpBranch %19
%19 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%17 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%18 = OpLoad %v4float %BaseColor
OpStore %v %18
OpBranch %19
%19 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, IfThenElsePhiTrue) {
  // Test handling of phi in merge block after dead branch elimination.
  // Note: The SPIR-V has had store/load elimination and phi insertion
  //
  // #version 140
  //
  // void main()
  // {
  //     vec4 v;
  //     if (true)
  //       v = vec4(0.0,0.0,0.0,0.0);
  //     else
  //       v = vec4(1.0,1.0,1.0,1.0);
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%5 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
)";

  const std::string before =
      R"(%main = OpFunction %void None %5
%17 = OpLabel
OpSelectionMerge %18 None
OpBranchConditional %true %19 %20
%19 = OpLabel
OpBranch %18
%20 = OpLabel
OpBranch %18
%18 = OpLabel
%21 = OpPhi %v4float %12 %19 %14 %20
OpStore %gl_FragColor %21
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %5
%17 = OpLabel
OpBranch %19
%19 = OpLabel
OpBranch %18
%18 = OpLabel
OpStore %gl_FragColor %12
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, IfThenElsePhiFalse) {
  // Test handling of phi in merge block after dead branch elimination.
  // Note: The SPIR-V has had store/load elimination and phi insertion
  //
  // #version 140
  //
  // void main()
  // {
  //     vec4 v;
  //     if (true)
  //       v = vec4(0.0,0.0,0.0,0.0);
  //     else
  //       v = vec4(1.0,1.0,1.0,1.0);
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%5 = OpTypeFunction %void
%bool = OpTypeBool
%false = OpConstantFalse %bool
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
)";

  const std::string before =
      R"(%main = OpFunction %void None %5
%17 = OpLabel
OpSelectionMerge %18 None
OpBranchConditional %false %19 %20
%19 = OpLabel
OpBranch %18
%20 = OpLabel
OpBranch %18
%18 = OpLabel
%21 = OpPhi %v4float %12 %19 %14 %20
OpStore %gl_FragColor %21
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %5
%17 = OpLabel
OpBranch %20
%20 = OpLabel
OpBranch %18
%18 = OpLabel
OpStore %gl_FragColor %14
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, CompoundIfThenElseFalse) {
  // #version 140
  //
  // layout(std140) uniform U_t
  // {
  //     bool g_B ;
  // } ;
  //
  // void main()
  // {
  //     vec4 v;
  //     if (false) {
  //       if (g_B)
  //         v = vec4(0.0,0.0,0.0,0.0);
  //       else
  //         v = vec4(1.0,1.0,1.0,1.0);
  //     } else {
  //       if (g_B)
  //         v = vec4(1.0,1.0,1.0,1.0);
  //       else
  //         v = vec4(0.0,0.0,0.0,0.0);
  //     }
  //     gl_FragColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %U_t "U_t"
OpMemberName %U_t 0 "g_B"
OpName %_ ""
OpName %v "v"
OpName %gl_FragColor "gl_FragColor"
OpMemberDecorate %U_t 0 Offset 0
OpDecorate %U_t Block
OpDecorate %_ DescriptorSet 0
%void = OpTypeVoid
%8 = OpTypeFunction %void
%bool = OpTypeBool
%false = OpConstantFalse %bool
%uint = OpTypeInt 32 0
%U_t = OpTypeStruct %uint
%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
%_ = OpVariable %_ptr_Uniform_U_t Uniform
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_Uniform_uint = OpTypePointer Uniform %uint
%uint_0 = OpConstant %uint 0
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%21 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string before =
      R"(%main = OpFunction %void None %8
%25 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpSelectionMerge %26 None
OpBranchConditional %false %27 %28
%27 = OpLabel
%29 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
%30 = OpLoad %uint %29
%31 = OpINotEqual %bool %30 %uint_0
OpSelectionMerge %32 None
OpBranchConditional %31 %33 %34
%33 = OpLabel
OpStore %v %21
OpBranch %32
%34 = OpLabel
OpStore %v %23
OpBranch %32
%32 = OpLabel
OpBranch %26
%28 = OpLabel
%35 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
%36 = OpLoad %uint %35
%37 = OpINotEqual %bool %36 %uint_0
OpSelectionMerge %38 None
OpBranchConditional %37 %39 %40
%39 = OpLabel
OpStore %v %23
OpBranch %38
%40 = OpLabel
OpStore %v %21
OpBranch %38
%38 = OpLabel
OpBranch %26
%26 = OpLabel
%41 = OpLoad %v4float %v
OpStore %gl_FragColor %41
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %8
%25 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
OpBranch %28
%28 = OpLabel
%35 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
%36 = OpLoad %uint %35
%37 = OpINotEqual %bool %36 %uint_0
OpSelectionMerge %38 None
OpBranchConditional %37 %39 %40
%40 = OpLabel
OpStore %v %21
OpBranch %38
%39 = OpLabel
OpStore %v %23
OpBranch %38
%38 = OpLabel
OpBranch %26
%26 = OpLabel
%41 = OpLoad %v4float %v
OpStore %gl_FragColor %41
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, PreventOrphanMerge) {
  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %BaseColor "BaseColor"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%7 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%true = OpConstantTrue %bool
%float_0_5 = OpConstant %float 0.5
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%16 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%17 = OpLoad %v4float %BaseColor
OpStore %v %17
OpSelectionMerge %18 None
OpBranchConditional %true %19 %20
%19 = OpLabel
OpKill
%20 = OpLabel
%21 = OpLoad %v4float %v
%22 = OpVectorTimesScalar %v4float %21 %float_0_5
OpStore %v %22
OpBranch %18
%18 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%16 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%17 = OpLoad %v4float %BaseColor
OpStore %v %17
OpBranch %19
%19 = OpLabel
OpKill
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, HandleOrphanMerge) {
  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %foo_ "foo("
OpName %gl_FragColor "gl_FragColor"
OpDecorate %gl_FragColor Location 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%9 = OpTypeFunction %v4float
%bool = OpTypeBool
%true = OpConstantTrue %bool
%float_0 = OpConstant %float 0
%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%15 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%main = OpFunction %void None %6
%17 = OpLabel
%18 = OpFunctionCall %v4float %foo_
OpStore %gl_FragColor %18
OpReturn
OpFunctionEnd
)";

  const std::string before =
      R"(%foo_ = OpFunction %v4float None %9
%19 = OpLabel
OpSelectionMerge %20 None
OpBranchConditional %true %21 %22
%21 = OpLabel
OpReturnValue %13
%22 = OpLabel
OpReturnValue %15
%20 = OpLabel
%23 = OpUndef %v4float
OpReturnValue %23
OpFunctionEnd
)";

  const std::string after =
      R"(%foo_ = OpFunction %v4float None %9
%19 = OpLabel
OpBranch %21
%21 = OpLabel
OpReturnValue %13
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, KeepContinueTargetWhenKillAfterMerge) {
  // #version 450
  // void main() {
  //   bool c;
  //   bool d;
  //   while(c) {
  //     if(d) {
  //      continue;
  //     }
  //     if(false) {
  //      continue;
  //     }
  //     discard;
  //   }
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %c "c"
OpName %d "d"
%void = OpTypeVoid
%6 = OpTypeFunction %void
%bool = OpTypeBool
%_ptr_Function_bool = OpTypePointer Function %bool
%false = OpConstantFalse %bool
)";

  const std::string before =
      R"(%main = OpFunction %void None %6
%10 = OpLabel
%c = OpVariable %_ptr_Function_bool Function
%d = OpVariable %_ptr_Function_bool Function
OpBranch %11
%11 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %bool %c
OpBranchConditional %15 %16 %12
%16 = OpLabel
%17 = OpLoad %bool %d
OpSelectionMerge %18 None
OpBranchConditional %17 %19 %18
%19 = OpLabel
OpBranch %13
%18 = OpLabel
OpSelectionMerge %20 None
OpBranchConditional %false %21 %20
%21 = OpLabel
OpBranch %13
%20 = OpLabel
OpKill
%13 = OpLabel
OpBranch %11
%12 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %6
%10 = OpLabel
%c = OpVariable %_ptr_Function_bool Function
%d = OpVariable %_ptr_Function_bool Function
OpBranch %11
%11 = OpLabel
OpLoopMerge %12 %13 None
OpBranch %14
%14 = OpLabel
%15 = OpLoad %bool %c
OpBranchConditional %15 %16 %12
%16 = OpLabel
%17 = OpLoad %bool %d
OpSelectionMerge %18 None
OpBranchConditional %17 %19 %18
%19 = OpLabel
OpBranch %13
%18 = OpLabel
OpBranch %20
%20 = OpLabel
OpKill
%13 = OpLabel
OpBranch %11
%12 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, DecorateDeleted) {
  // Note: SPIR-V hand-edited to add decoration
  // #version 140
  //
  // in vec4 BaseColor;
  //
  // void main()
  // {
  //     vec4 v = BaseColor;
  //     if (false)
  //       v = v * vec4(0.5,0.5,0.5,0.5);
  //     gl_FragColor = v;
  // }

  const std::string predefs_before =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %BaseColor "BaseColor"
OpName %gl_FragColor "gl_FragColor"
OpDecorate %22 RelaxedPrecision
%void = OpTypeVoid
%7 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%false = OpConstantFalse %bool
%float_0_5 = OpConstant %float 0.5
%15 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string predefs_after =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
OpName %main "main"
OpName %v "v"
OpName %BaseColor "BaseColor"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%false = OpConstantFalse %bool
%float_0_5 = OpConstant %float 0.5
%16 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%17 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%18 = OpLoad %v4float %BaseColor
OpStore %v %18
OpSelectionMerge %19 None
OpBranchConditional %false %20 %19
%20 = OpLabel
%21 = OpLoad %v4float %v
%22 = OpFMul %v4float %21 %15
OpStore %v %22
OpBranch %19
%19 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %8
%18 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%19 = OpLoad %v4float %BaseColor
OpStore %v %19
OpBranch %20
%20 = OpLabel
%23 = OpLoad %v4float %v
OpStore %gl_FragColor %23
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs_before + before,
                                            predefs_after + after, true, true);
}

TEST_F(DeadBranchElimTest, LoopInDeadBranch) {
  // #version 450
  //
  // layout(location = 0) in vec4 BaseColor;
  // layout(location = 0) out vec4 OutColor;
  //
  // void main()
  // {
  //     vec4 v = BaseColor;
  //     if (false)
  //       for (int i=0; i<3; i++)
  //         v = v * 0.5;
  //     OutColor = v;
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %BaseColor %OutColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %v "v"
OpName %BaseColor "BaseColor"
OpName %i "i"
OpName %OutColor "OutColor"
OpDecorate %BaseColor Location 0
OpDecorate %OutColor Location 0
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
%bool = OpTypeBool
%false = OpConstantFalse %bool
%int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
%int_0 = OpConstant %int 0
%int_3 = OpConstant %int 3
%float_0_5 = OpConstant %float 0.5
%int_1 = OpConstant %int 1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%OutColor = OpVariable %_ptr_Output_v4float Output
)";

  const std::string before =
      R"(%main = OpFunction %void None %8
%22 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%i = OpVariable %_ptr_Function_int Function
%23 = OpLoad %v4float %BaseColor
OpStore %v %23
OpSelectionMerge %24 None
OpBranchConditional %false %25 %24
%25 = OpLabel
OpStore %i %int_0
OpBranch %26
%26 = OpLabel
OpLoopMerge %27 %28 None
OpBranch %29
%29 = OpLabel
%30 = OpLoad %int %i
%31 = OpSLessThan %bool %30 %int_3
OpBranchConditional %31 %32 %27
%32 = OpLabel
%33 = OpLoad %v4float %v
%34 = OpVectorTimesScalar %v4float %33 %float_0_5
OpStore %v %34
OpBranch %28
%28 = OpLabel
%35 = OpLoad %int %i
%36 = OpIAdd %int %35 %int_1
OpStore %i %36
OpBranch %26
%27 = OpLabel
OpBranch %24
%24 = OpLabel
%37 = OpLoad %v4float %v
OpStore %OutColor %37
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %8
%22 = OpLabel
%v = OpVariable %_ptr_Function_v4float Function
%i = OpVariable %_ptr_Function_int Function
%23 = OpLoad %v4float %BaseColor
OpStore %v %23
OpBranch %24
%24 = OpLabel
%37 = OpLoad %v4float %v
OpStore %OutColor %37
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, SwitchLiveCase) {
  // #version 450
  //
  // layout (location=0) in vec4 BaseColor;
  // layout (location=0) out vec4 OutColor;
  //
  // void main()
  // {
  //     switch (1) {
  //       case 0:
  //         OutColor = vec4(0.0,0.0,0.0,0.0);
  //         break;
  //       case 1:
  //         OutColor = vec4(0.125,0.125,0.125,0.125);
  //         break;
  //       case 2:
  //         OutColor = vec4(0.25,0.25,0.25,0.25);
  //         break;
  //       default:
  //         OutColor = vec4(1.0,1.0,1.0,1.0);
  //     }
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %OutColor %BaseColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %OutColor "OutColor"
OpName %BaseColor "BaseColor"
OpDecorate %OutColor Location 0
OpDecorate %BaseColor Location 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_1 = OpConstant %int 1
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%OutColor = OpVariable %_ptr_Output_v4float Output
%float_0 = OpConstant %float 0
%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_0_125 = OpConstant %float 0.125
%15 = OpConstantComposite %v4float %float_0_125 %float_0_125 %float_0_125 %float_0_125
%float_0_25 = OpConstant %float 0.25
%17 = OpConstantComposite %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25
%float_1 = OpConstant %float 1
%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
)";

  const std::string before =
      R"(%main = OpFunction %void None %6
%21 = OpLabel
OpSelectionMerge %22 None
OpSwitch %int_1 %23 0 %24 1 %25 2 %26
%23 = OpLabel
OpStore %OutColor %19
OpBranch %22
%24 = OpLabel
OpStore %OutColor %13
OpBranch %22
%25 = OpLabel
OpStore %OutColor %15
OpBranch %22
%26 = OpLabel
OpStore %OutColor %17
OpBranch %22
%22 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %6
%21 = OpLabel
OpBranch %25
%25 = OpLabel
OpStore %OutColor %15
OpBranch %22
%22 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, SwitchLiveDefault) {
  // #version 450
  //
  // layout (location=0) in vec4 BaseColor;
  // layout (location=0) out vec4 OutColor;
  //
  // void main()
  // {
  //     switch (7) {
  //       case 0:
  //         OutColor = vec4(0.0,0.0,0.0,0.0);
  //         break;
  //       case 1:
  //         OutColor = vec4(0.125,0.125,0.125,0.125);
  //         break;
  //       case 2:
  //         OutColor = vec4(0.25,0.25,0.25,0.25);
  //         break;
  //       default:
  //         OutColor = vec4(1.0,1.0,1.0,1.0);
  //     }
  // }

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %OutColor %BaseColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %OutColor "OutColor"
OpName %BaseColor "BaseColor"
OpDecorate %OutColor Location 0
OpDecorate %BaseColor Location 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_7 = OpConstant %int 7
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%OutColor = OpVariable %_ptr_Output_v4float Output
%float_0 = OpConstant %float 0
%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_0_125 = OpConstant %float 0.125
%15 = OpConstantComposite %v4float %float_0_125 %float_0_125 %float_0_125 %float_0_125
%float_0_25 = OpConstant %float 0.25
%17 = OpConstantComposite %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25
%float_1 = OpConstant %float 1
%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
)";

  const std::string before =
      R"(%main = OpFunction %void None %6
%21 = OpLabel
OpSelectionMerge %22 None
OpSwitch %int_7 %23 0 %24 1 %25 2 %26
%23 = OpLabel
OpStore %OutColor %19
OpBranch %22
%24 = OpLabel
OpStore %OutColor %13
OpBranch %22
%25 = OpLabel
OpStore %OutColor %15
OpBranch %22
%26 = OpLabel
OpStore %OutColor %17
OpBranch %22
%22 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %6
%21 = OpLabel
OpBranch %23
%23 = OpLabel
OpStore %OutColor %19
OpBranch %22
%22 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, SwitchLiveCaseBreakFromLoop) {
  // This sample does not directly translate to GLSL/HLSL as
  // direct breaks from a loop cannot be made from a switch.
  // This construct is currently formed by inlining a function
  // containing early returns from the cases of a switch. The
  // function is wrapped in a one-trip loop and returns are
  // translated to branches to the loop's merge block.

  const std::string predefs =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %OutColor %BaseColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %oc "oc"
OpName %OutColor "OutColor"
OpName %BaseColor "BaseColor"
OpDecorate %OutColor Location 0
OpDecorate %BaseColor Location 0
%void = OpTypeVoid
%7 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%int = OpTypeInt 32 1
%int_1 = OpConstant %int 1
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0
%17 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_0_125 = OpConstant %float 0.125
%19 = OpConstantComposite %v4float %float_0_125 %float_0_125 %float_0_125 %float_0_125
%float_0_25 = OpConstant %float 0.25
%21 = OpConstantComposite %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25
%float_1 = OpConstant %float 1
%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%OutColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%BaseColor = OpVariable %_ptr_Input_v4float Input
)";

  const std::string before =
      R"(%main = OpFunction %void None %7
%26 = OpLabel
%oc = OpVariable %_ptr_Function_v4float Function
OpBranch %27
%27 = OpLabel
OpLoopMerge %28 %29 None
OpBranch %30
%30 = OpLabel
OpSelectionMerge %31 None
OpSwitch %int_1 %31 0 %32 1 %33 2 %34
%32 = OpLabel
OpStore %oc %17
OpBranch %28
%33 = OpLabel
OpStore %oc %19
OpBranch %28
%34 = OpLabel
OpStore %oc %21
OpBranch %28
%31 = OpLabel
OpStore %oc %23
OpBranch %28
%29 = OpLabel
OpBranchConditional %false %27 %28
%28 = OpLabel
%35 = OpLoad %v4float %oc
OpStore %OutColor %35
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(%main = OpFunction %void None %7
%26 = OpLabel
%oc = OpVariable %_ptr_Function_v4float Function
OpBranch %27
%27 = OpLabel
OpLoopMerge %28 %29 None
OpBranch %30
%30 = OpLabel
OpBranch %33
%33 = OpLabel
OpStore %oc %19
OpBranch %28
%29 = OpLabel
OpBranch %27
%28 = OpLabel
%35 = OpLoad %v4float %oc
OpStore %OutColor %35
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(predefs + before, predefs + after,
                                            true, true);
}

TEST_F(DeadBranchElimTest, LeaveContinueBackedge) {
  const std::string text = R"(
; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
; CHECK: [[continue]] = OpLabel
; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]]
; CHECK-NEXT: [[merge]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
%bool = OpTypeBool
%false = OpConstantFalse %bool
%void = OpTypeVoid
%funcTy = OpTypeFunction %void
%func = OpFunction %void None %funcTy
%1 = OpLabel
OpBranch %2
%2 = OpLabel
OpLoopMerge %3 %4 None
OpBranch %4
%4 = OpLabel
; Be careful we don't remove the backedge to %2 despite never taking it.
OpBranchConditional %false %2 %3
%3 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, LeaveContinueBackedgeExtraBlock) {
  const std::string text = R"(
; CHECK: OpBranch [[header:%\w+]]
; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
; CHECK-NEXT: OpBranch [[continue]]
; CHECK-NEXT: [[continue]] = OpLabel
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[extra:%\w+]] [[merge]]
; CHECK-NEXT: [[extra]] = OpLabel
; CHECK-NEXT: OpBranch [[header]]
; CHECK-NEXT: [[merge]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
%bool = OpTypeBool
%false = OpConstantFalse %bool
%void = OpTypeVoid
%funcTy = OpTypeFunction %void
%func = OpFunction %void None %funcTy
%1 = OpLabel
OpBranch %2
%2 = OpLabel
OpLoopMerge %3 %4 None
OpBranch %4
%4 = OpLabel
; Be careful we don't remove the backedge to %2 despite never taking it.
OpBranchConditional %false %5 %3
; This block remains live despite being unreachable.
%5 = OpLabel
OpBranch %2
%3 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, RemovePhiWithUnreachableContinue) {
  const std::string text = R"(
; CHECK: [[entry:%\w+]] = OpLabel
; CHECK-NEXT: OpBranch [[header:%\w+]]
; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
; CHECK-NEXT: OpBranch [[ret:%\w+]]
; CHECK-NEXT: [[ret]] = OpLabel
; CHECK-NEXT: OpReturn
; CHECK: [[continue]] = OpLabel
; CHECK-NEXT: OpBranch [[header]]
; CHECK: [[merge]] = OpLabel
; CHECK-NEXT: OpUnreachable
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%bool = OpTypeBool
%false = OpConstantFalse %bool
%true = OpConstantTrue %bool
%void = OpTypeVoid
%funcTy = OpTypeFunction %void
%func = OpFunction %void None %funcTy
%1 = OpLabel
OpBranch %2
%2 = OpLabel
%phi = OpPhi %bool %false %1 %true %continue
OpLoopMerge %merge %continue None
OpBranch %3
%3 = OpLabel
OpReturn
%continue = OpLabel
OpBranch %2
%merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, UnreachableLoopMergeAndContinueTargets) {
  const std::string text = R"(
; CHECK: [[undef:%\w+]] = OpUndef %bool
; CHECK: OpSelectionMerge [[header:%\w+]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] [[else_lab:%\w+]]
; CHECK: OpPhi %bool %false [[if_lab]] %false [[else_lab]] [[undef]] [[continue:%\w+]]
; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
; CHECK-NEXT: OpBranch [[ret:%\w+]]
; CHECK-NEXT: [[ret]] = OpLabel
; CHECK-NEXT: OpReturn
; CHECK: [[continue]] = OpLabel
; CHECK-NEXT: OpBranch [[header]]
; CHECK: [[merge]] = OpLabel
; CHECK-NEXT: OpUnreachable
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%bool = OpTypeBool
%false = OpConstantFalse %bool
%true = OpConstantTrue %bool
%void = OpTypeVoid
%funcTy = OpTypeFunction %void
%func = OpFunction %void None %funcTy
%1 = OpLabel
%c = OpUndef %bool
OpSelectionMerge %2 None
OpBranchConditional %c %if %else
%if = OpLabel
OpBranch %2
%else = OpLabel
OpBranch %2
%2 = OpLabel
%phi = OpPhi %bool %false %if %false %else %true %continue
OpLoopMerge %merge %continue None
OpBranch %3
%3 = OpLabel
OpReturn
%continue = OpLabel
OpBranch %2
%merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}
TEST_F(DeadBranchElimTest, EarlyReconvergence) {
  const std::string text = R"(
; CHECK-NOT: OpBranchConditional
; CHECK: [[logical:%\w+]] = OpLogicalOr
; CHECK-NOT: OpPhi
; CHECK: OpLogicalAnd {{%\w+}} {{%\w+}} [[logical]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%bool = OpTypeBool
%false = OpConstantFalse %bool
%true = OpConstantTrue %bool
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
OpSelectionMerge %2 None
OpBranchConditional %false %3 %4
%3 = OpLabel
%12 = OpLogicalNot %bool %true
OpBranch %2
%4 = OpLabel
OpSelectionMerge %14 None
OpBranchConditional %false %5 %6
%5 = OpLabel
%10 = OpLogicalAnd %bool %true %false
OpBranch %7
%6 = OpLabel
%11 = OpLogicalOr %bool %true %false
OpBranch %7
%7 = OpLabel
; This phi is in a block preceding the merge %14!
%8 = OpPhi %bool %10 %5 %11 %6
OpBranch %14
%14 = OpLabel
OpBranch %2
%2 = OpLabel
%9 = OpPhi %bool %12 %3 %8 %14
%13 = OpLogicalAnd %bool %true %9
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksFloating) {
  const std::string text = R"(
; CHECK: OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpReturn
; CHECK-NEXT: OpFunctionEnd
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%1 = OpTypeFunction %void
%func = OpFunction %void None %1
%2 = OpLabel
OpReturn
%3 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksFloatingJoin) {
  const std::string text = R"(
; CHECK: OpFunction
; CHECK-NEXT: OpFunctionParameter
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpReturn
; CHECK-NEXT: OpFunctionEnd
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%false = OpConstantFalse %bool
%true = OpConstantTrue %bool
%1 = OpTypeFunction %void %bool
%func = OpFunction %void None %1
%bool_param = OpFunctionParameter %bool
%2 = OpLabel
OpReturn
%3 = OpLabel
OpSelectionMerge %6 None
OpBranchConditional %bool_param %4 %5
%4 = OpLabel
OpBranch %6
%5 = OpLabel
OpBranch %6
%6 = OpLabel
%7 = OpPhi %bool %true %4 %false %6
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksDeadPhi) {
  const std::string text = R"(
; CHECK: OpFunction
; CHECK-NEXT: OpFunctionParameter
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpBranch [[label:%\w+]]
; CHECK-NEXT: [[label]] = OpLabel
; CHECK-NEXT: OpLogicalNot %bool %true
; CHECK-NEXT: OpReturn
; CHECK-NEXT: OpFunctionEnd
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%false = OpConstantFalse %bool
%true = OpConstantTrue %bool
%1 = OpTypeFunction %void %bool
%func = OpFunction %void None %1
%bool_param = OpFunctionParameter %bool
%2 = OpLabel
OpBranch %3
%4 = OpLabel
OpBranch %3
%3 = OpLabel
%5 = OpPhi %bool %true %2 %false %4
%6 = OpLogicalNot %bool %5
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksPartiallyDeadPhi) {
  const std::string text = R"(
; CHECK: OpFunction
; CHECK-NEXT: [[param:%\w+]] = OpFunctionParameter
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpBranchConditional [[param]] [[merge:%\w+]] [[br:%\w+]]
; CHECK-NEXT: [[merge]] = OpLabel
; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool %true %2 %false [[br]]
; CHECK-NEXT: OpLogicalNot %bool [[phi]]
; CHECK-NEXT: OpReturn
; CHECK-NEXT: [[br]] = OpLabel
; CHECK-NEXT: OpBranch [[merge]]
; CHECK-NEXT: OpFunctionEnd
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%false = OpConstantFalse %bool
%true = OpConstantTrue %bool
%1 = OpTypeFunction %void %bool
%func = OpFunction %void None %1
%bool_param = OpFunctionParameter %bool
%2 = OpLabel
OpBranchConditional %bool_param %3 %7
%7 = OpLabel
OpBranch %3
%4 = OpLabel
OpBranch %3
%3 = OpLabel
%5 = OpPhi %bool %true %2 %false %7 %false %4
%6 = OpLogicalNot %bool %5
OpReturn
OpFunctionEnd
)";

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

TEST_F(DeadBranchElimTest, LiveHeaderDeadPhi) {
  const std::string text = R"(
; CHECK: OpLabel
; CHECK-NOT: OpBranchConditional
; CHECK-NOT: OpPhi
; CHECK: OpLogicalNot %bool %false
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
OpSelectionMerge %3 None
OpBranchConditional %true %2 %3
%2 = OpLabel
OpBranch %3
%3 = OpLabel
%5 = OpPhi %bool %true %3 %false %2
%6 = OpLogicalNot %bool %5
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, ExtraBackedgeBlocksLive) {
  const std::string text = R"(
; CHECK: [[entry:%\w+]] = OpLabel
; CHECK-NOT: OpSelectionMerge
; CHECK: OpBranch [[header:%\w+]]
; CHECK-NEXT: [[header]] = OpLabel
; CHECK-NEXT: OpPhi %bool %true [[entry]] %false [[backedge:%\w+]]
; CHECK-NEXT: OpLoopMerge
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%func_ty = OpTypeFunction %void %bool
%func = OpFunction %void None %func_ty
%param = OpFunctionParameter %bool
%entry = OpLabel
OpSelectionMerge %if_merge None
; This dead branch is included to ensure the pass does work.
OpBranchConditional %false %if_merge %loop_header
%loop_header = OpLabel
; Both incoming edges are live, so the phi should be untouched.
%phi = OpPhi %bool %true %entry %false %backedge
OpLoopMerge %loop_merge %continue None
OpBranchConditional %param %loop_merge %continue
%continue = OpLabel
OpBranch %backedge
%backedge = OpLabel
OpBranch %loop_header
%loop_merge = OpLabel
OpBranch %if_merge
%if_merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, ExtraBackedgeBlocksUnreachable) {
  const std::string text = R"(
; CHECK: [[entry:%\w+]] = OpLabel
; CHECK-NEXT: OpBranch [[header:%\w+]]
; CHECK-NEXT: [[header]] = OpLabel
; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
; CHECK-NEXT: OpBranch [[merge]]
; CHECK-NEXT: [[merge]] = OpLabel
; CHECK-NEXT: OpReturn
; CHECK-NEXT: [[continue]] = OpLabel
; CHECK-NEXT: OpBranch [[header]]
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%func_ty = OpTypeFunction %void %bool
%func = OpFunction %void None %func_ty
%param = OpFunctionParameter %bool
%entry = OpLabel
OpBranch %loop_header
%loop_header = OpLabel
; Since the continue is unreachable, %backedge will be removed. The phi will
; instead require an edge from %continue.
%phi = OpPhi %bool %true %entry %false %backedge
OpLoopMerge %merge %continue None
OpBranch %merge
%continue = OpLabel
OpBranch %backedge
%backedge = OpLabel
OpBranch %loop_header
%merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, NoUnnecessaryChanges) {
  const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%undef = OpUndef %bool
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpBranch %2
%2 = OpLabel
OpLoopMerge %4 %5 None
OpBranch %6
%6 = OpLabel
OpReturn
%5 = OpLabel
OpBranch %2
%4 = OpLabel
OpUnreachable
OpFunctionEnd
)";

  auto result = SinglePassRunToBinary<DeadBranchElimPass>(text, true);
  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}

TEST_F(DeadBranchElimTest, ExtraBackedgePartiallyDead) {
  const std::string text = R"(
; CHECK: OpLabel
; CHECK: [[header:%\w+]] = OpLabel
; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
; CHECK: [[merge]] = OpLabel
; CHECK: [[continue]] = OpLabel
; CHECK: OpBranch [[extra:%\w+]]
; CHECK: [[extra]] = OpLabel
; CHECK-NOT: OpSelectionMerge
; CHECK-NEXT: OpBranch [[else:%\w+]]
; CHECK-NEXT: [[else]] = OpLabel
; CHECK-NEXT: OpLogicalOr
; CHECK-NEXT: OpBranch [[backedge:%\w+]]
; CHECK-NEXT: [[backedge:%\w+]] = OpLabel
; CHECK-NEXT: OpBranch [[header]]
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpName %func "func"
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%func_ty = OpTypeFunction %void %bool
%func = OpFunction %void None %func_ty
%param = OpFunctionParameter %bool
%entry = OpLabel
OpBranch %loop_header
%loop_header = OpLabel
OpLoopMerge %loop_merge %continue None
OpBranchConditional %param %loop_merge %continue
%continue = OpLabel
OpBranch %extra
%extra = OpLabel
OpSelectionMerge %backedge None
OpBranchConditional %false %then %else
%then = OpLabel
%and = OpLogicalAnd %bool %true %false
OpBranch %backedge
%else = OpLabel
%or = OpLogicalOr %bool %true %false
OpBranch %backedge
%backedge = OpLabel
OpBranch %loop_header
%loop_merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, UnreachableContinuePhiInMerge) {
  const std::string text = R"(
; CHECK: [[entry:%\w+]] = OpLabel
; CHECK-NEXT: OpBranch [[header:%\w+]]
; CHECK-NEXT: [[header]] = OpLabel
; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None
; CHECK-NEXT: OpBranch [[label:%\w+]]
; CHECK-NEXT: [[label]] = OpLabel
; CHECK-NEXT: [[fadd:%\w+]] = OpFAdd
; CHECK-NEXT: OpBranch [[label:%\w+]]
; CHECK-NEXT: [[label]] = OpLabel
; CHECK-NEXT: OpBranch [[merge]]
; CHECK-NEXT: [[continue]] = OpLabel
; CHECK-NEXT: OpBranch [[header]]
; CHECK-NEXT: [[merge]] = OpLabel
; CHECK-NEXT: OpStore {{%\w+}} [[fadd]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %main "main" %o
               OpExecutionMode %main OriginUpperLeft
               OpSource GLSL 430
               OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
               OpSourceExtension "GL_GOOGLE_include_directive"
               OpName %main "main"
               OpName %o "o"
               OpName %S "S"
               OpMemberName %S 0 "a"
               OpName %U_t "U_t"
               OpMemberName %U_t 0 "g_F"
               OpMemberName %U_t 1 "g_F2"
               OpDecorate %o Location 0
               OpMemberDecorate %S 0 Offset 0
               OpMemberDecorate %U_t 0 Volatile
               OpMemberDecorate %U_t 0 Offset 0
               OpMemberDecorate %U_t 1 Offset 4
               OpDecorate %U_t BufferBlock
       %void = OpTypeVoid
          %7 = OpTypeFunction %void
      %float = OpTypeFloat 32
%_ptr_Function_float = OpTypePointer Function %float
    %float_0 = OpConstant %float 0
        %int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
      %int_0 = OpConstant %int 0
     %int_10 = OpConstant %int 10
       %bool = OpTypeBool
       %true = OpConstantTrue %bool
    %float_1 = OpConstant %float 1
    %float_5 = OpConstant %float 5
      %int_1 = OpConstant %int 1
%_ptr_Output_float = OpTypePointer Output %float
          %o = OpVariable %_ptr_Output_float Output
          %S = OpTypeStruct %float
        %U_t = OpTypeStruct %S %S
%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
       %main = OpFunction %void None %7
         %22 = OpLabel
               OpBranch %23
         %23 = OpLabel
         %24 = OpPhi %float %float_0 %22 %25 %26
         %27 = OpPhi %int %int_0 %22 %28 %26
               OpLoopMerge %29 %26 None
               OpBranch %40
         %40 = OpLabel
         %25 = OpFAdd %float %24 %float_1
               OpSelectionMerge %30 None
               OpBranchConditional %true %31 %30
         %31 = OpLabel
               OpBranch %29
         %30 = OpLabel
               OpBranch %26
         %26 = OpLabel
         %28 = OpIAdd %int %27 %int_1
         %32 = OpSLessThan %bool %27 %int_10
; continue block branches to the header or another none dead block.
               OpBranchConditional %32 %23 %29
         %29 = OpLabel
         %33 = OpPhi %float %24 %26 %25 %31
               OpStore %o %33
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, NonStructuredIf) {
  const std::string text = R"(
; CHECK-NOT: OpBranchConditional
OpCapability Kernel
OpCapability Linkage
OpMemoryModel Logical OpenCL
OpDecorate %func LinkageAttributes "func" Export
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%entry = OpLabel
OpBranchConditional %true %then %else
%then = OpLabel
OpBranch %final
%else = OpLabel
OpBranch %final
%final = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, ReorderBlocks) {
  const std::string text = R"(
; CHECK: OpLabel
; CHECK: OpBranch [[label:%\w+]]
; CHECK: [[label:%\w+]] = OpLabel
; CHECK-NEXT: OpLogicalNot
; CHECK-NEXT: OpBranch [[label:%\w+]]
; CHECK: [[label]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
OpSelectionMerge %3 None
OpBranchConditional %true %2 %3
%3 = OpLabel
OpReturn
%2 = OpLabel
%not = OpLogicalNot %bool %true
OpBranch %3
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, ReorderBlocksMultiple) {
  // Checks are not important. The validation post optimization is the
  // important part.
  const std::string text = R"(
; CHECK: OpLabel
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
OpSelectionMerge %3 None
OpBranchConditional %true %2 %3
%3 = OpLabel
OpReturn
%2 = OpLabel
OpBranch %4
%4 = OpLabel
OpBranch %3
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, ReorderBlocksMultiple2) {
  // Checks are not important. The validation post optimization is the
  // important part.
  const std::string text = R"(
; CHECK: OpLabel
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
OpSelectionMerge %3 None
OpBranchConditional %true %2 %3
%3 = OpLabel
OpBranch %5
%5 = OpLabel
OpReturn
%2 = OpLabel
OpBranch %4
%4 = OpLabel
OpBranch %3
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, SelectionMergeWithEarlyExit1) {
  // Checks  that if a selection merge construct contains a conditional branch
  // to the merge node, then the OpSelectionMerge instruction is positioned
  // correctly.
  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
%undef_bool = OpUndef %bool
)";

  const std::string body =
      R"(
; CHECK: OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpBranch [[taken_branch:%\w+]]
; CHECK-NEXT: [[taken_branch]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[merge:%\w+]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[merge]] {{%\w+}}
%main = OpFunction %void None %func_type
%entry_bb = OpLabel
OpSelectionMerge %outer_merge None
OpBranchConditional %true %bb1 %bb3
%bb1 = OpLabel
OpBranchConditional %undef_bool %outer_merge %bb2
%bb2 = OpLabel
OpBranch %outer_merge
%bb3 = OpLabel
OpBranch %outer_merge
%outer_merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
}

TEST_F(DeadBranchElimTest, SelectionMergeWithEarlyExit2) {
  // Checks  that if a selection merge construct contains a conditional branch
  // to the merge node, then the OpSelectionMerge instruction is positioned
  // correctly.
  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
%undef_bool = OpUndef %bool
)";

  const std::string body =
      R"(
; CHECK: OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NEXT: OpBranch [[bb1:%\w+]]
; CHECK-NEXT: [[bb1]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[inner_merge:%\w+]]
; CHECK: [[inner_merge]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[outer_merge:%\w+]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[outer_merge]:%\w+]] {{%\w+}}
; CHECK: [[outer_merge]] = OpLabel
; CHECK-NEXT: OpReturn
%main = OpFunction %void None %func_type
%entry_bb = OpLabel
OpSelectionMerge %outer_merge None
OpBranchConditional %true %bb1 %bb5
%bb1 = OpLabel
OpSelectionMerge %inner_merge None
OpBranchConditional %undef_bool %bb2 %bb3
%bb2 = OpLabel
OpBranch %inner_merge
%bb3 = OpLabel
OpBranch %inner_merge
%inner_merge = OpLabel
OpBranchConditional %undef_bool %outer_merge %bb4
%bb4 = OpLabel
OpBranch %outer_merge
%bb5 = OpLabel
OpBranch %outer_merge
%outer_merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
}

TEST_F(DeadBranchElimTest, SelectionMergeWithConditionalExit) {
  // Checks that if a selection merge construct contains a conditional branch
  // to the merge node, then we keep the OpSelectionMerge on that branch.
  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 [[sel_merge:%\w+]] None
; CHECK-NEXT: OpSwitch {{%\w+}} [[sel_merge]] 1 [[bb3:%\w+]]
; CHECK: [[bb3]] = OpLabel
; CHECK-NEXT: OpBranch [[sel_merge]]
; 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
OpSwitch %undef_int %sel_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, SelectionMergeWithExitToLoop) {
  // Checks  that if a selection merge construct contains a conditional branch
  // to a loop surrounding the selection merge, then we do not keep the
  // OpSelectionMerge instruction.
  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
%undef_bool = OpUndef %bool
)";

  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: OpBranchConditional {{%\w+}} [[bb3:%\w+]] [[loop_merge]]
; 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
OpBranchConditional %undef_bool %bb3 %loop_merge
%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, SelectionMergeWithExitToLoopContinue) {
  // Checks  that if a selection merge construct contains a conditional branch
  // to continue of a loop surrounding the selection merge, then we do not keep
  // the OpSelectionMerge instruction.
  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
%undef_bool = OpUndef %bool
)";

  const std::string body =
      R"(;
; CHECK: OpLabel
; CHECK: [[loop_header:%\w+]] = OpLabel
; 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: OpBranchConditional {{%\w+}} [[bb3:%\w+]] [[loop_cont]]
; CHECK: [[bb3]] = OpLabel
; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
; CHECK: [[sel_merge]] = OpLabel
; CHECK-NEXT: OpBranch [[loop_merge]]
; CHECK: [[loop_cont]] = OpLabel
; CHECK-NEXT: OpBranch [[loop_header]]
; 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
OpBranchConditional %undef_bool %bb3 %cont
%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, SelectionMergeWithExitToLoop2) {
  // Same as |SelectionMergeWithExitToLoop|, except the switch goes to the loop
  // merge or the selection merge.  In this case, we do not need an
  // OpSelectionMerge 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
%undef_bool = OpUndef %bool
)";

  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: OpBranchConditional {{%\w+}} [[sel_merge:%\w+]] [[loop_merge]]
; 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
OpBranchConditional %undef_bool %sel_merge %loop_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, SelectionMergeWithExitToLoopContinue2) {
  // Same as |SelectionMergeWithExitToLoopContinue|, except the branch goes to
  // the loop continue or the selection merge.  In this case, we do not need an
  // OpSelectionMerge 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
%undef_bool = OpUndef %bool
)";

  const std::string body =
      R"(
; CHECK: OpLabel
; CHECK: [[loop_header:%\w+]] = OpLabel
; 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: OpBranchConditional {{%\w+}} [[sel_merge:%\w+]] [[loop_cont]]
; CHECK: [[sel_merge]] = OpLabel
; CHECK-NEXT: OpBranch [[loop_merge]]
; CHECK: [[loop_cont]] = OpLabel
; CHECK: OpBranch [[loop_header]]
; 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
OpBranchConditional %undef_bool %sel_merge %cont
%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, SelectionMergeWithExitToLoop3) {
  // Checks that if a selection merge construct contains a conditional 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"
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
%undef_bool = OpUndef %bool
)";

  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 [[sel_merge:%\w+]] None
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
; CHECK: [[bb3]] = OpLabel
; CHECK-NEXT: OpBranch [[sel_merge]]
; 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
;OpSwitch %undef_int %sel_merge 0 %loop_merge 1 %bb3
OpBranchConditional %undef_bool %sel_merge %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, SelectionMergeWithExitToLoopContinue3) {
  // Checks that if a selection merge construct contains a conditional 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"
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
%undef_bool = OpUndef %bool
)";

  const std::string body =
      R"(
; CHECK: OpLabel
; CHECK: [[loop_header:%\w+]] = OpLabel
; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_continue:%\w+]]
; CHECK-NEXT: OpBranch [[bb1:%\w+]]
; CHECK: [[bb1]] = OpLabel
; CHECK-NEXT: OpBranch [[bb2:%\w+]]
; CHECK: [[bb2]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[sel_merge]] [[bb3:%\w+]]
; CHECK: [[bb3]] = OpLabel
; CHECK-NEXT: OpBranch [[sel_merge]]
; CHECK: [[sel_merge]] = OpLabel
; CHECK-NEXT: OpBranch [[loop_merge]]
; CHECK: [[loop_continue]] = OpLabel
; CHECK-NEXT: OpBranch [[loop_header]]
; 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
OpBranchConditional %undef_bool %sel_merge %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, SelectionMergeSameAsLoopContinue) {
  // 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_bool = OpUndef %bool
)";

  const std::string body =
      R"(
; CHECK: OpLabel
; CHECK: [[loop_header:%\w+]] = OpLabel
; 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 [[loop_cont]]
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[bb3:%\w+]] [[loop_cont]]
; CHECK: [[bb3]] = OpLabel
; CHECK-NEXT: OpBranch [[loop_cont]]
; CHECK: [[loop_cont]] = OpLabel
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[loop_header]] [[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 %cont None
OpBranchConditional %true %bb2 %bb4
%bb2 = OpLabel
OpBranchConditional %undef_bool %bb3 %cont
%bb3 = OpLabel
OpBranch %cont
%bb4 = OpLabel
OpBranch %cont
%cont = OpLabel
OpBranchConditional %undef_bool %loop_header %loop_merge
%loop_merge = OpLabel
OpReturn
OpFunctionEnd
)";

  // The selection merge in the loop naming the continue target as merge is
  // invalid, but handled by this pass so validation is disabled.
  SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, false);
}

TEST_F(DeadBranchElimTest, SelectionMergeWithNestedLoop) {
  const std::string body =
      R"(
; CHECK: OpSelectionMerge [[merge1:%\w+]]
; CHECK: [[merge1]] = OpLabel
; CHECK-NEXT: OpBranch [[preheader:%\w+]]
; CHECK: [[preheader]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranch [[header:%\w+]]
; CHECK: [[header]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpLoopMerge [[merge2:%\w+]]
; CHECK: [[merge2]] = OpLabel
; CHECK-NEXT: OpUnreachable
                 OpCapability Shader
            %1 = OpExtInstImport "GLSL.std.450"
                 OpMemoryModel Logical GLSL450
                 OpEntryPoint Fragment %main "main"
                 OpExecutionMode %main OriginUpperLeft
                 OpSource ESSL 310
                 OpName %main "main"
                 OpName %h "h"
                 OpName %i "i"
         %void = OpTypeVoid
            %3 = OpTypeFunction %void
         %bool = OpTypeBool
  %_ptr_Function_bool = OpTypePointer Function %bool
         %true = OpConstantTrue %bool
          %int = OpTypeInt 32 1
  %_ptr_Function_int = OpTypePointer Function %int
        %int_1 = OpConstant %int 1
        %int_0 = OpConstant %int 0
           %27 = OpUndef %bool
         %main = OpFunction %void None %3
            %5 = OpLabel
            %h = OpVariable %_ptr_Function_bool Function
            %i = OpVariable %_ptr_Function_int Function
                 OpSelectionMerge %11 None
                 OpBranchConditional %27 %10 %11
           %10 = OpLabel
                 OpBranch %11
           %11 = OpLabel
                 OpSelectionMerge %14 None
                 OpBranchConditional %true %13 %14
           %13 = OpLabel
                 OpStore %i %int_1
                 OpBranch %19
           %19 = OpLabel
                 OpLoopMerge %21 %22 None
                 OpBranch %23
           %23 = OpLabel
           %26 = OpSGreaterThan %bool %int_1 %int_0
                 OpBranchConditional %true %20 %21
           %20 = OpLabel
                 OpBranch %22
           %22 = OpLabel
                 OpBranch %19
           %21 = OpLabel
                 OpBranch %14
           %14 = OpLabel
                 OpReturn
                 OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(body, true);
}

TEST_F(DeadBranchElimTest, DontFoldBackedge) {
  const std::string body =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
%void = OpTypeVoid
%4 = OpTypeFunction %void
%bool = OpTypeBool
%false = OpConstantFalse %bool
%2 = OpFunction %void None %4
%7 = OpLabel
OpBranch %8
%8 = OpLabel
OpLoopMerge %9 %10 None
OpBranch %11
%11 = OpLabel
%12 = OpUndef %bool
OpSelectionMerge %10 None
OpBranchConditional %12 %13 %10
%13 = OpLabel
OpBranch %9
%10 = OpLabel
OpBranch %14
%14 = OpLabel
OpBranchConditional %false %8 %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(body, body, true);
}

TEST_F(DeadBranchElimTest, FoldBackedgeToHeader) {
  const std::string body =
      R"(
; CHECK: OpLabel
; CHECK: [[header:%\w+]] = OpLabel
; CHECK-NEXT: OpLoopMerge {{%\w+}} [[cont:%\w+]]
; CHECK: [[cont]] = OpLabel
; This branch may not be in the continue block, but must come after it.
; CHECK: OpBranch [[header]]
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %2 "main"
OpExecutionMode %2 OriginUpperLeft
%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
%12 = OpUndef %bool
OpSelectionMerge %10 None
OpBranchConditional %12 %13 %10
%13 = OpLabel
OpBranch %9
%10 = OpLabel
OpBranch %14
%14 = OpLabel
OpBranchConditional %true %8 %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";

  // The selection merge in the loop naming the continue target as merge is
  // invalid, but handled by this pass so validation is disabled.
  SinglePassRunAndMatch<DeadBranchElimPass>(body, false);
}

TEST_F(DeadBranchElimTest, UnreachableMergeAndContinueSameBlock) {
  const std::string spirv = R"(
; CHECK: OpLabel
; CHECK: [[outer:%\w+]] = OpLabel
; CHECK-NEXT: OpLoopMerge [[outer_merge:%\w+]] [[outer_cont:%\w+]] None
; CHECK-NEXT: OpBranch [[inner:%\w+]]
; CHECK: [[inner]] = OpLabel
; 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
; CHECK-NEXT: OpUnreachable
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%bool = OpTypeBool
%true = OpConstantTrue %bool
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpBranch %outer_loop
%outer_loop = OpLabel
OpLoopMerge %outer_merge %outer_continue None
OpBranch %inner_loop
%inner_loop = OpLabel
OpLoopMerge %inner_merge %inner_continue None
OpBranch %inner_body
%inner_body = OpLabel
OpSelectionMerge %inner_continue None
OpBranchConditional %true %ret %inner_continue
%ret = OpLabel
OpReturn
%inner_continue = OpLabel
OpBranchConditional %true %inner_merge %inner_loop
%inner_merge = OpLabel
OpBranch %outer_continue
%outer_continue = OpLabel
OpBranchConditional %true %outer_merge %outer_loop
%outer_merge = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
}

// Fold a switch with a nested break.  The only case should be the default.
TEST_F(DeadBranchElimTest, FoldSwitchWithNestedBreak) {
  const std::string spirv = R"(
; CHECK: OpSwitch %int_3 [[case_bb:%\w+]]{{[[:space:]]}}
; CHECK: [[case_bb]] = OpLabel
; CHECK-NEXT: OpUndef
; CHECK-NEXT: OpSelectionMerge
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %2 "main"
               OpSource GLSL 450
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
        %int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
      %int_3 = OpConstant %int 3
      %int_1 = OpConstant %int 1
       %bool = OpTypeBool
          %2 = OpFunction %void None %4
         %10 = OpLabel
               OpSelectionMerge %11 None
               OpSwitch %int_3 %12 3 %13
         %12 = OpLabel
               OpBranch %11
         %13 = OpLabel
         %14 = OpUndef %bool
               OpSelectionMerge %15 None
               OpBranchConditional %14 %16 %15
         %16 = OpLabel
               OpBranch %11
         %15 = OpLabel
               OpBranch %11
         %11 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
}

TEST_F(DeadBranchElimTest, FoldBranchWithBreakToSwitch) {
  const std::string spirv = R"(
; CHECK: OpSelectionMerge [[sel_merge:%\w+]]
; CHECK-NEXT: OpSwitch {{%\w+}} {{%\w+}} 3 [[bb:%\w+]]
; CHECK: [[bb]] = OpLabel
; CHECK-NEXT: OpBranch [[bb2:%\w+]]
; CHECK: [[bb2]] = OpLabel
; CHECK-NOT: OpSelectionMerge
; CHECK: OpFunctionEnd
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %2 "main"
               OpSource GLSL 450
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
        %int = OpTypeInt 32 1
%_ptr_Function_int = OpTypePointer Function %int
      %int_3 = OpConstant %int 3
      %int_1 = OpConstant %int 1
       %bool = OpTypeBool
       %true = OpConstantTrue %bool
          %2 = OpFunction %void None %4
         %10 = OpLabel
  %undef_int = OpUndef %int
               OpSelectionMerge %11 None
               OpSwitch %undef_int %12 3 %13
         %12 = OpLabel
               OpBranch %11
         %13 = OpLabel
               OpSelectionMerge %15 None
               OpBranchConditional %true %16 %15
         %16 = OpLabel
         %14 = OpUndef %bool
               OpBranchConditional %14 %11 %17
         %17 = OpLabel
               OpBranch %15
         %15 = OpLabel
               OpBranch %11
         %11 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(spirv, true);
}

TEST_F(DeadBranchElimTest, IfInSwitch) {
  // #version 310 es
  //
  // void main()
  // {
  //  switch(0)
  //  {
  //   case 0:
  //   if(false)
  //   {
  //   }
  //   else
  //   {
  //   }
  //  }
  // }

  const std::string before =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource ESSL 310
OpName %main "main"
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%bool = OpTypeBool
%false = OpConstantFalse %bool
%main = OpFunction %void None %3
%5 = OpLabel
OpSelectionMerge %9 None
OpSwitch %int_0 %9 0 %8
%8 = OpLabel
OpSelectionMerge %13 None
OpBranchConditional %false %12 %13
%12 = OpLabel
OpBranch %13
%13 = OpLabel
OpBranch %9
%9 = OpLabel
OpReturn
OpFunctionEnd
)";

  const std::string after =
      R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource ESSL 310
OpName %main "main"
%void = OpTypeVoid
%4 = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%bool = OpTypeBool
%false = OpConstantFalse %bool
%main = OpFunction %void None %4
%9 = OpLabel
OpBranch %11
%11 = OpLabel
OpBranch %12
%12 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(before, after, true, true);
}

TEST_F(DeadBranchElimTest, BreakInNestedHeaderWithSingleCase) {
  const std::string text = R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
%void = OpTypeVoid
%4 = OpTypeFunction %void
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%8 = OpUndef %bool
%main = OpFunction %void None %4
%9 = OpLabel
OpSelectionMerge %10 None
OpSwitch %uint_0 %11
%11 = OpLabel
OpSelectionMerge %12 None
OpBranchConditional %8 %10 %12
%12 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(text, text, true, true);
}

TEST_F(DeadBranchElimTest, BreakInNestedHeaderWithTwoCases) {
  const std::string text = R"(
; CHECK: OpSelectionMerge [[merge:%\w+]] None
; CHECK-NEXT: OpSwitch %uint_0 [[bb:%\w+\n]]
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
%void = OpTypeVoid
%4 = OpTypeFunction %void
%bool = OpTypeBool
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%8 = OpUndef %bool
%main = OpFunction %void None %4
%9 = OpLabel
OpSelectionMerge %10 None
OpSwitch %uint_0 %11 1 %12
%11 = OpLabel
OpSelectionMerge %13 None
OpBranchConditional %8 %10 %13
%13 = OpLabel
OpBranch %10
%12 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, DebugInformation) {
  const std::string text = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
%ext = OpExtInstImport "OpenCL.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %gl_FragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 140
%name = OpString "test"
OpName %main "main"
OpName %gl_FragColor "gl_FragColor"
%void = OpTypeVoid
%5 = OpTypeFunction %void
%bool = OpTypeBool
%true = OpConstantTrue %bool
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
%float_0 = OpConstant %float 0

; CHECK: [[value:%\w+]] = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
%float_1 = OpConstant %float 1
%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%gl_FragColor = OpVariable %_ptr_Output_v4float Output
%_ptr_Input_v4float = OpTypePointer Input %v4float
%uint = OpTypeInt 32 0
%uint_32 = OpConstant %uint 32

%null_expr = OpExtInst %void %ext DebugExpression
%src = OpExtInst %void %ext DebugSource %name
%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL
%ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %void
%dbg_main = OpExtInst %void %ext DebugFunction %name %ty %src 0 0 %cu %name FlagIsProtected|FlagIsPrivate 0 %main

; CHECK: [[bb1:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugLexicalBlock [[src:%\w+]] 1 0 [[dbg_main:%\w+]]
; CHECK: [[bb2:%\w+]] = OpExtInst %void [[ext]] DebugLexicalBlock [[src]] 2 0 [[dbg_main]]
; CHECK: [[bb3:%\w+]] = OpExtInst %void [[ext]] DebugLexicalBlock [[src]] 3 0 [[dbg_main]]
%bb1 = OpExtInst %void %ext DebugLexicalBlock %src 1 0 %dbg_main
%bb2 = OpExtInst %void %ext DebugLexicalBlock %src 2 0 %dbg_main
%bb3 = OpExtInst %void %ext DebugLexicalBlock %src 3 0 %dbg_main

%dbg_f = OpExtInst %void %ext DebugTypeBasic %name %uint_32 Float
; CHECK: [[dbg_foo:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable {{%\w+}} [[ty:%\w+]] [[src]] 0 0 [[dbg_main]]
%dbg_foo = OpExtInst %void %ext DebugLocalVariable %name %dbg_f %src 0 0 %dbg_main FlagIsLocal
; CHECK: [[dbg_bar:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable {{%\w+}} [[ty]] [[src]] 1 0 [[bb3]]
%dbg_bar = OpExtInst %void %ext DebugLocalVariable %name %dbg_f %src 1 0 %bb3 FlagIsLocal

%main = OpFunction %void None %5
%17 = OpLabel
; CHECK-NOT: DebugScope [[dbg_main]]
; CHECK-NOT: OpLine {{%\w+}} 0 0
%scope0 = OpExtInst %void %ext DebugScope %dbg_main
OpLine %name 0 0
OpSelectionMerge %18 None
OpBranchConditional %true %19 %20
%19 = OpLabel
; CHECK: DebugScope [[bb1]]
; CHECK: OpLine {{%\w+}} 1 0
%scope1 = OpExtInst %void %ext DebugScope %bb1
OpLine %name 1 0
OpBranch %18
%20 = OpLabel
; CHECK-NOT: DebugScope [[bb2]]
; CHECK-NOT: OpLine {{%\w+}} 2 0
%scope2 = OpExtInst %void %ext DebugScope %bb2
OpLine %name 2 0
OpBranch %18
%18 = OpLabel

; CHECK: DebugScope [[bb3]]
; CHECK: OpLine {{%\w+}} 3 0
; CHECK: DebugValue [[dbg_foo]] [[value]]
; CHECK: OpLine {{%\w+}} 4 0
; CHECK: OpStore %gl_FragColor [[value]]
; CHECK: DebugDeclare [[dbg_bar]] %gl_FragColor
; CHECK: DebugValue [[dbg_bar]] [[value]]
%scope3 = OpExtInst %void %ext DebugScope %bb3
OpLine %name 3 0
%21 = OpPhi %v4float %12 %19 %14 %20
%decl0 = OpExtInst %void %ext DebugValue %dbg_foo %21 %null_expr
OpLine %name 4 0
OpStore %gl_FragColor %21
%decl1 = OpExtInst %void %ext DebugDeclare %dbg_bar %gl_FragColor %null_expr
%decl2 = OpExtInst %void %ext DebugValue %dbg_bar %21 %null_expr
OpLine %name 5 0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, DontTransferDecorations) {
  // When replacing %4 with %14, we don't want %14 to inherit %4's decorations.
  const std::string text = R"(
; CHECK-NOT: OpDecorate {{%\w+}} RelaxedPrecision
; CHECK: [[div:%\w+]] = OpFDiv
; CHECK: {{%\w+}} = OpCopyObject %float [[div]]
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %2 "main"
               OpExecutionMode %2 OriginUpperLeft
          %3 = OpString "STEVEN"
               OpDecorate %4 RelaxedPrecision
      %float = OpTypeFloat 32
       %uint = OpTypeInt 32 0
       %void = OpTypeVoid
    %float_1 = OpConstant %float 1
     %uint_0 = OpConstant %uint 0
         %10 = OpTypeFunction %void
          %2 = OpFunction %void None %10
         %11 = OpLabel
               OpSelectionMerge %12 None
               OpSwitch %uint_0 %13
         %13 = OpLabel
         %14 = OpFDiv %float %float_1 %float_1
               OpLine %3 0 0
               OpBranch %12
         %15 = OpLabel
               OpBranch %12
         %12 = OpLabel
          %4 = OpPhi %float %float_1 %15 %14 %13
         %16 = OpCopyObject %float %4
               OpReturn
               OpFunctionEnd
)";

  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
}

TEST_F(DeadBranchElimTest, FunctionDeclaration) {
  // Make sure the pass works with a function declaration that is called.
  const std::string text = R"(OpCapability Addresses
OpCapability Linkage
OpCapability Kernel
OpCapability Int8
%1 = OpExtInstImport "OpenCL.std"
OpMemoryModel Physical64 OpenCL
OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
OpExecutionMode %2 ContractionOff
OpSource Unknown 0
OpDecorate %3 LinkageAttributes "julia_error_7712" Import
%void = OpTypeVoid
%5 = OpTypeFunction %void
%3 = OpFunction %void None %5
OpFunctionEnd
%2 = OpFunction %void None %5
%6 = OpLabel
%7 = OpFunctionCall %void %3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<DeadBranchElimPass>(text, text, false);
}

// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
//    More complex control flow
//    Others?

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